Merge branch 'master' of https://github.com/Azure/cosmos-explorer into migrate/QueryTablesTab

This commit is contained in:
hardiknai-techm
2021-07-17 11:57:29 +05:30
73 changed files with 660 additions and 732 deletions

View File

@@ -71,7 +71,6 @@ src/Explorer/DataSamples/ContainerSampleGenerator.test.ts
src/Explorer/DataSamples/ContainerSampleGenerator.ts src/Explorer/DataSamples/ContainerSampleGenerator.ts
src/Explorer/DataSamples/DataSamplesUtil.test.ts src/Explorer/DataSamples/DataSamplesUtil.test.ts
src/Explorer/DataSamples/DataSamplesUtil.ts src/Explorer/DataSamples/DataSamplesUtil.ts
src/Explorer/Explorer.tsx
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts
src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts
@@ -83,11 +82,6 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.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/Menus/ContextMenu.ts
src/Explorer/MostRecentActivity/MostRecentActivity.ts src/Explorer/MostRecentActivity/MostRecentActivity.ts
src/Explorer/Notebook/NotebookClientV2.ts src/Explorer/Notebook/NotebookClientV2.ts
@@ -141,7 +135,6 @@ src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts src/Explorer/Tabs/TriggerTab.ts
src/Explorer/Tabs/UserDefinedFunctionTab.ts src/Explorer/Tabs/UserDefinedFunctionTab.ts
src/Explorer/Tree/AccessibleVerticalList.ts src/Explorer/Tree/AccessibleVerticalList.ts
src/Explorer/Tree/Collection.test.ts
src/Explorer/Tree/Collection.ts src/Explorer/Tree/Collection.ts
src/Explorer/Tree/ConflictId.ts src/Explorer/Tree/ConflictId.ts
src/Explorer/Tree/DocumentId.ts src/Explorer/Tree/DocumentId.ts
@@ -150,65 +143,32 @@ src/Explorer/Tree/ResourceTokenCollection.ts
src/Explorer/Tree/StoredProcedure.ts src/Explorer/Tree/StoredProcedure.ts
src/Explorer/Tree/TreeComponents.ts src/Explorer/Tree/TreeComponents.ts
src/Explorer/Tree/Trigger.ts src/Explorer/Tree/Trigger.ts
src/Explorer/Tree/UserDefinedFunction.ts
src/Explorer/WaitsForTemplateViewModel.ts src/Explorer/WaitsForTemplateViewModel.ts
src/GitHub/GitHubClient.test.ts src/GitHub/GitHubClient.test.ts
src/GitHub/GitHubClient.ts src/GitHub/GitHubClient.ts
src/GitHub/GitHubConnector.ts src/GitHub/GitHubConnector.ts
src/GitHub/GitHubContentProvider.test.ts
src/GitHub/GitHubContentProvider.ts
src/GitHub/GitHubOAuthService.ts src/GitHub/GitHubOAuthService.ts
src/Index.ts src/Index.ts
src/Juno/JunoClient.test.ts src/Juno/JunoClient.test.ts
src/Juno/JunoClient.ts src/Juno/JunoClient.ts
src/Platform/Hosted/Authorization.ts src/Platform/Hosted/Authorization.ts
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
src/ReactDevTools.ts src/ReactDevTools.ts
src/Shared/Constants.ts src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.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/Shared/appInsights.ts
src/SparkClusterManager/ArcadiaResourceManager.ts src/SparkClusterManager/ArcadiaResourceManager.ts
src/SparkClusterManager/SparkClusterManager.ts src/SparkClusterManager/SparkClusterManager.ts
src/Terminal/JupyterLabAppFactory.ts src/Terminal/JupyterLabAppFactory.ts
src/Terminal/NotebookAppContracts.d.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/applyExplorerBindings.ts
src/global.d.ts src/global.d.ts
src/setupTests.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/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.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/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.tsx src/Explorer/Controls/TreeComponent/TreeComponent.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.test.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
@@ -216,43 +176,19 @@ src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.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.test.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.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/NotebookComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.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/NotebookComponent/contents/index.tsx
src/Explorer/Notebook/NotebookRenderer/AzureTheme.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.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/draggable/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/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/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/ResourceTreeAdapter.tsx
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
__mocks__/monaco-editor.ts __mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx

View File

@@ -1,4 +1,3 @@
{ {
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com", "JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
"enableSchemaAnalyzer": true
} }

View File

@@ -94,7 +94,7 @@ export class Flights {
public static readonly MongoIndexEditor = "mongoindexeditor"; public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing"; public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest"; public static readonly AutoscaleTest = "autoscaletest";
public static readonly SchemaAnalyzer = "schemaanalyzer"; public static readonly PartitionKeyTest = "partitionkeytest";
} }
export class AfecFeatures { export class AfecFeatures {

View File

@@ -1,6 +1,6 @@
import * as HeadersUtility from "./HeadersUtility"; import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
import * as HeadersUtility from "./HeadersUtility";
describe("Headers Utility", () => { describe("Headers Utility", () => {
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => { describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {

View File

@@ -27,7 +27,6 @@ export interface ConfigContext {
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string; armAPIVersion?: string;
allowedJunoOrigins: string[]; allowedJunoOrigins: string[];
enableSchemaAnalyzer: boolean;
msalRedirectURI?: string; msalRedirectURI?: string;
} }
@@ -63,7 +62,6 @@ let configContext: Readonly<ConfigContext> = {
"https://tools-staging.cosmos.azure.com", "https://tools-staging.cosmos.azure.com",
"https://localhost", "https://localhost",
], ],
enableSchemaAnalyzer: false,
}; };
export function resetConfigContext(): void { export function resetConfigContext(): void {

View File

@@ -9,6 +9,7 @@ export interface DatabaseAccount {
export interface DatabaseAccountExtendedProperties { export interface DatabaseAccountExtendedProperties {
documentEndpoint?: string; documentEndpoint?: string;
disableLocalAuth?: boolean;
tableEndpoint?: string; tableEndpoint?: string;
gremlinEndpoint?: string; gremlinEndpoint?: string;
cassandraEndpoint?: string; cassandraEndpoint?: string;

View File

@@ -109,7 +109,7 @@ export const createCollectionContextMenuButton = (
iconSrc: AddUdfIcon, iconSrc: AddUdfIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, undefined); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection);
}, },
label: "New UDF", label: "New UDF",
}); });

View File

@@ -8,7 +8,9 @@ import TriangleDownIcon from "../../../../images/Triangle-down.svg";
import TriangleRightIcon from "../../../../images/Triangle-right.svg"; import TriangleRightIcon from "../../../../images/Triangle-right.svg";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
export interface AccordionComponentProps {} export interface AccordionComponentProps {
children: React.ReactNode;
}
export class AccordionComponent extends React.Component<AccordionComponentProps> { export class AccordionComponent extends React.Component<AccordionComponentProps> {
public render(): JSX.Element { public render(): JSX.Element {
@@ -78,7 +80,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
); );
} }
private onHeaderClick = (_event: React.MouseEvent<HTMLDivElement>): void => { private onHeaderClick = (): void => {
this.setState({ isExpanded: !this.state.isExpanded }); this.setState({ isExpanded: !this.state.isExpanded });
}; };

View File

@@ -121,8 +121,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
if (!this.dropdownElt || !this.expandButtonElt) { if (!this.dropdownElt || !this.expandButtonElt) {
return; 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 { private onKeyPress(event: React.KeyboardEvent): boolean {

View File

@@ -56,7 +56,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
this.editor = editor; this.editor = editor;
const queryEditorModel = this.editor.getModel(); const queryEditorModel = this.editor.getModel();
if (!this.props.isReadOnly && this.props.onContentChanged) { if (!this.props.isReadOnly && this.props.onContentChanged) {
queryEditorModel.onDidChangeContent((e: monaco.editor.IModelContentChangedEvent) => { queryEditorModel.onDidChangeContent(() => {
const queryEditorModel = this.editor.getModel(); const queryEditorModel = this.editor.getModel();
this.props.onContentChanged(queryEditorModel.getValue()); this.props.onContentChanged(queryEditorModel.getValue());
}); });

View File

@@ -56,7 +56,7 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
return ( return (
<> <>
<div className={"paneMainContent"}>{content}</div> <div>{content}</div>
{!this.props.showAuthorizeAccess && ( {!this.props.showAuthorizeAccess && (
<> <>
<div className={"paneFooter"} style={ContentFooterStyle}> <div className={"paneFooter"} style={ContentFooterStyle}>

View File

@@ -1,154 +1,90 @@
import { shallow } from "enzyme";
import React from "react";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import { NotebookTerminalComponent } from "./NotebookTerminalComponent"; import { NotebookTerminalComponent, NotebookTerminalComponentProps } from "./NotebookTerminalComponent";
const createTestDatabaseAccount = (): DataModels.DatabaseAccount => { const testAccount: DataModels.DatabaseAccount = {
return { id: "id",
id: "testId", kind: "kind",
kind: "testKind", location: "location",
location: "testLocation", name: "name",
name: "testName", properties: {
properties: { documentEndpoint: "https://testDocumentEndpoint.azure.com/",
cassandraEndpoint: null, },
documentEndpoint: "https://testDocumentEndpoint.azure.com/", type: "type",
gremlinEndpoint: null,
tableEndpoint: null,
},
type: "testType",
};
}; };
const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => { const testMongo32Account: DataModels.DatabaseAccount = {
return { ...testAccount,
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null,
},
type: "testType",
};
}; };
const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => { const testMongo36Account: DataModels.DatabaseAccount = {
return { ...testAccount,
id: "testId", properties: {
kind: "testKind", mongoEndpoint: "https://testMongoEndpoint.azure.com/",
location: "testLocation", },
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null,
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
},
type: "testType",
};
}; };
const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => { const testCassandraAccount: DataModels.DatabaseAccount = {
return { ...testAccount,
id: "testId", properties: {
kind: "testKind", cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
location: "testLocation", },
name: "testName",
properties: {
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
documentEndpoint: null,
gremlinEndpoint: null,
tableEndpoint: null,
},
type: "testType",
};
}; };
const createTerminal = (): NotebookTerminalComponent => { const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
return new NotebookTerminalComponent({ authToken: "authToken",
notebookServerInfo: { notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/",
},
databaseAccount: createTestDatabaseAccount(),
});
}; };
const createMongo32Terminal = (): NotebookTerminalComponent => { const testMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
return new NotebookTerminalComponent({ authToken: "authToken",
notebookServerInfo: { notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
},
databaseAccount: createTestMongo32DatabaseAccount(),
});
}; };
const createMongo36Terminal = (): NotebookTerminalComponent => { const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
return new NotebookTerminalComponent({ authToken: "authToken",
notebookServerInfo: { notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
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", () => { describe("NotebookTerminalComponent", () => {
it("getTerminalParams: Test for terminal", () => { it("renders terminal", () => {
const terminal: NotebookTerminalComponent = createTerminal(); const props: NotebookTerminalComponentProps = {
const params: Map<string, string> = terminal.getTerminalParams(); databaseAccount: testAccount,
notebookServerInfo: testNotebookServerInfo,
};
expect(params).toEqual( const wrapper = shallow(<NotebookTerminalComponent {...props} />);
new Map<string, string>([["terminal", "true"]]) expect(wrapper).toMatchSnapshot();
);
}); });
it("getTerminalParams: Test for Mongo 3.2 terminal", () => { it("renders mongo 3.2 shell", () => {
const terminal: NotebookTerminalComponent = createMongo32Terminal(); const props: NotebookTerminalComponentProps = {
const params: Map<string, string> = terminal.getTerminalParams(); databaseAccount: testMongo32Account,
notebookServerInfo: testMongoNotebookServerInfo,
};
expect(params).toEqual( const wrapper = shallow(<NotebookTerminalComponent {...props} />);
new Map<string, string>([ expect(wrapper).toMatchSnapshot();
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host],
])
);
}); });
it("getTerminalParams: Test for Mongo 3.6 terminal", () => { it("renders mongo 3.6 shell", () => {
const terminal: NotebookTerminalComponent = createMongo36Terminal(); const props: NotebookTerminalComponentProps = {
const params: Map<string, string> = terminal.getTerminalParams(); databaseAccount: testMongo36Account,
notebookServerInfo: testMongoNotebookServerInfo,
};
expect(params).toEqual( const wrapper = shallow(<NotebookTerminalComponent {...props} />);
new Map<string, string>([ expect(wrapper).toMatchSnapshot();
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host],
])
);
}); });
it("getTerminalParams: Test for Cassandra terminal", () => { it("renders cassandra shell", () => {
const terminal: NotebookTerminalComponent = createCassandraTerminal(); const props: NotebookTerminalComponentProps = {
const params: Map<string, string> = terminal.getTerminalParams(); databaseAccount: testCassandraAccount,
notebookServerInfo: testCassandraNotebookServerInfo,
};
expect(params).toEqual( const wrapper = shallow(<NotebookTerminalComponent {...props} />);
new Map<string, string>([ expect(wrapper).toMatchSnapshot();
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host],
])
);
}); });
}); });

View File

@@ -2,12 +2,12 @@
* Wrapper around Notebook server terminal * Wrapper around Notebook server terminal
*/ */
import postRobot from "post-robot";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as StringUtils from "../../../Utils/StringUtils"; import { TerminalProps } from "../../../Terminal/TerminalProps";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { TerminalQueryParams } from "../../../Common/Constants"; import * as StringUtils from "../../../Utils/StringUtils";
import { handleError } from "../../../Common/ErrorHandlingUtils";
export interface NotebookTerminalComponentProps { export interface NotebookTerminalComponentProps {
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo; notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
@@ -15,79 +15,69 @@ export interface NotebookTerminalComponentProps {
} }
export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> { export class NotebookTerminalComponent extends React.Component<NotebookTerminalComponentProps> {
private terminalWindow: Window;
constructor(props: NotebookTerminalComponentProps) { constructor(props: NotebookTerminalComponentProps) {
super(props); super(props);
} }
componentDidMount(): void {
this.sendPropsToTerminalFrame();
}
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div className="notebookTerminalContainer"> <div className="notebookTerminalContainer">
<iframe <iframe
title="Terminal to Notebook Server" title="Terminal to Notebook Server"
src={NotebookTerminalComponent.createNotebookAppSrc(this.props.notebookServerInfo, this.getTerminalParams())} onLoad={(event) => this.handleFrameLoad(event)}
src="terminal.html"
/> />
</div> </div>
); );
} }
public getTerminalParams(): Map<string, string> { handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
let params: Map<string, string> = new Map<string, string>(); this.terminalWindow = (event.target as HTMLIFrameElement).contentWindow;
params.set(TerminalQueryParams.Terminal, "true"); this.sendPropsToTerminalFrame();
const terminalEndpoint: string = this.tryGetTerminalEndpoint();
if (terminalEndpoint) {
params.set(TerminalQueryParams.TerminalEndpoint, terminalEndpoint);
}
return params;
} }
public tryGetTerminalEndpoint(): string | null { sendPropsToTerminalFrame(): void {
let terminalEndpoint: string | null; if (!this.terminalWindow) {
return;
}
const notebookServerEndpoint: string = this.props.notebookServerInfo.notebookServerEndpoint; 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,
});
}
public tryGetTerminalEndpoint(): string | undefined {
let terminalEndpoint: string | undefined;
const notebookServerEndpoint = this.props.notebookServerInfo?.notebookServerEndpoint;
if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) { if (StringUtils.endsWith(notebookServerEndpoint, "mongo")) {
let mongoShellEndpoint: string = this.props.databaseAccount.properties.mongoEndpoint; // mongoEndpoint is only available for Mongo 3.6 and higher, fallback to documentEndpoint otherwise
if (!mongoShellEndpoint) { terminalEndpoint =
// mongoEndpoint is only available for Mongo 3.6 and higher. this.props.databaseAccount?.properties.mongoEndpoint || this.props.databaseAccount?.properties.documentEndpoint;
// Fallback to documentEndpoint otherwise.
mongoShellEndpoint = this.props.databaseAccount.properties.documentEndpoint;
}
terminalEndpoint = mongoShellEndpoint;
} else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) { } else if (StringUtils.endsWith(notebookServerEndpoint, "cassandra")) {
terminalEndpoint = this.props.databaseAccount.properties.cassandraEndpoint; terminalEndpoint = this.props.databaseAccount?.properties.cassandraEndpoint;
} }
if (terminalEndpoint) { if (terminalEndpoint) {
return new URL(terminalEndpoint).host; return new URL(terminalEndpoint).host;
} }
return null;
}
public static createNotebookAppSrc( return undefined;
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;
} }
} }

View File

@@ -0,0 +1,49 @@
// 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>
`;

View File

@@ -42,6 +42,15 @@ exports[`SettingsComponent renders 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"gitHubOAuthService": GitHubOAuthService {
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"token": [Function],
},
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
@@ -113,6 +122,15 @@ exports[`SettingsComponent renders 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"gitHubOAuthService": GitHubOAuthService {
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"token": [Function],
},
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {

View File

@@ -11,18 +11,17 @@ import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility"
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { QueriesClient } from "../Common/QueriesClient"; import { QueriesClient } from "../Common/QueriesClient";
import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
import { useTabs } from "../hooks/useTabs"; import { useTabs } from "../hooks/useTabs";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; import { IGalleryItem } from "../Juno/JunoClient";
import { ExplorerSettings } from "../Shared/ExplorerSettings"; import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils"; import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { import {
get as getWorkspace, get as getWorkspace,
@@ -35,7 +34,7 @@ import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import * as ComponentRegisterer from "./ComponentRegisterer"; import "./ComponentRegisterer";
import { DialogProps, TextFieldProps, useDialog } from "./Controls/Dialog"; import { DialogProps, TextFieldProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
@@ -47,12 +46,8 @@ import type { NotebookPaneContent } from "./Notebook/NotebookManager";
import { NotebookUtil } from "./Notebook/NotebookUtil"; import { NotebookUtil } from "./Notebook/NotebookUtil";
import { useNotebook } from "./Notebook/useNotebook"; import { useNotebook } from "./Notebook/useNotebook";
import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane"; import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
import { GitHubReposPanel } from "./Panes/GitHubReposPanel/GitHubReposPanel";
import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane";
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel"; import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane"; import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane"; import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
@@ -70,8 +65,6 @@ import { useDatabases } from "./useDatabases";
import { useSelectedNode } from "./useSelectedNode"; import { useSelectedNode } from "./useSelectedNode";
BindingHandlersRegisterer.registerBindingHandlers(); BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
var tmp = ComponentRegisterer;
export default class Explorer { export default class Explorer {
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>; public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
@@ -143,13 +136,13 @@ export default class Explorer {
document.addEventListener( document.addEventListener(
"contextmenu", "contextmenu",
function (e) { (e) => {
e.preventDefault(); e.preventDefault();
}, },
false false
); );
$(function () { $(() => {
$(document.body).click(() => $(".commandDropdownContainer").hide()); $(document.body).click(() => $(".commandDropdownContainer").hide());
}); });
@@ -160,6 +153,7 @@ export default class Explorer {
case "Cassandra": case "Cassandra":
this.tableDataClient = new CassandraAPIDataClient(); this.tableDataClient = new CassandraAPIDataClient();
break; break;
default:
} }
this._initSettings(); this._initSettings();
@@ -219,10 +213,6 @@ export default class Explorer {
}); });
} }
if (configContext.enableSchemaAnalyzer) {
userContext.features.enableSchemaAnalyzer = true;
}
this.refreshExplorer(); this.refreshExplorer();
} }
@@ -330,19 +320,15 @@ export default class Explorer {
} }
} }
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => { public onRefreshDatabasesKeyPress = (source: string, event: KeyboardEvent): boolean => {
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
this.onRefreshResourcesClick(source, null); this.onRefreshResourcesClick();
return false; return false;
} }
return true; return true;
}; };
public onRefreshResourcesClick = (source: any, event: MouseEvent): void => { public onRefreshResourcesClick = (): void => {
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
description: "Refresh button clicked",
dataExplorerArea: Constants.Areas.ResourceTree,
});
userContext.authType === AuthType.ResourceToken userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases(); : this.refreshAllDatabases();
@@ -350,7 +336,7 @@ export default class Explorer {
}; };
// Facade // Facade
public provideFeedbackEmail = () => { public provideFeedbackEmail = (): void => {
window.open(Constants.Urls.feedbackEmail, "_blank"); window.open(Constants.Urls.feedbackEmail, "_blank");
}; };
@@ -564,7 +550,7 @@ export default class Explorer {
const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent); const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent);
promise promise
.then(() => this.resourceTree.triggerRender()) .then(() => this.resourceTree.triggerRender())
.catch((reason: any) => this.showOkModalDialog("Unable to upload file", reason)); .catch((reason) => this.showOkModalDialog("Unable to upload file", reason));
return promise; return promise;
} }
@@ -710,13 +696,13 @@ export default class Explorer {
const options: NotebookTabOptions = { const options: NotebookTabOptions = {
account: userContext.databaseAccount, account: userContext.databaseAccount,
tabKind: ViewModels.CollectionTabKind.NotebookV2, tabKind: ViewModels.CollectionTabKind.NotebookV2,
node: null, node: undefined,
title: notebookContentItem.name, title: notebookContentItem.name,
tabPath: notebookContentItem.path, tabPath: notebookContentItem.path,
collection: null, collection: undefined,
masterKey: userContext.masterKey || "", masterKey: userContext.masterKey || "",
isTabsContentExpanded: ko.observable(true), isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null, onLoadStartKey: undefined,
container: this, container: this,
notebookContentItem, notebookContentItem,
}; };
@@ -843,7 +829,7 @@ export default class Explorer {
clearMessage(); clearMessage();
}, },
(error: any) => { (error) => {
logConsoleError(`Could not download notebook ${getErrorMessage(error)}`); logConsoleError(`Could not download notebook ${getErrorMessage(error)}`);
clearMessage(); clearMessage();
} }
@@ -895,7 +881,7 @@ export default class Explorer {
return this.notebookManager?.notebookContentClient.deleteContentItem(item).then( return this.notebookManager?.notebookContentClient.deleteContentItem(item).then(
() => logConsoleInfo(`Successfully deleted: ${item.path}`), () => logConsoleInfo(`Successfully deleted: ${item.path}`),
(reason: any) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`) (reason) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`)
); );
} }
@@ -930,7 +916,7 @@ export default class Explorer {
return this.openNotebook(newFile); return this.openNotebook(newFile);
}) })
.then(() => this.resourceTree.triggerRender()) .then(() => this.resourceTree.triggerRender())
.catch((error: any) => { .catch((error) => {
const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`; const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`;
logConsoleError(errorMessage); logConsoleError(errorMessage);
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
@@ -988,12 +974,12 @@ export default class Explorer {
const newTab = new TerminalTab({ const newTab = new TerminalTab({
account: userContext.databaseAccount, account: userContext.databaseAccount,
tabKind: ViewModels.CollectionTabKind.Terminal, tabKind: ViewModels.CollectionTabKind.Terminal,
node: null, node: undefined,
title: `${title} ${index}`, title: `${title} ${index}`,
tabPath: `${title} ${index}`, tabPath: `${title} ${index}`,
collection: null, collection: undefined,
isTabsContentExpanded: ko.observable(true), isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null, onLoadStartKey: undefined,
container: this, container: this,
kind: kind, kind: kind,
index: index, index: index,
@@ -1013,7 +999,7 @@ export default class Explorer {
const galleryTab = useTabs const galleryTab = useTabs
.getState() .getState()
.getTabs(ViewModels.CollectionTabKind.Gallery) .getTabs(ViewModels.CollectionTabKind.Gallery)
.find((tab) => tab.tabTitle() == title); .find((tab) => tab.tabTitle() === title);
if (galleryTab instanceof GalleryTab) { if (galleryTab instanceof GalleryTab) {
useTabs.getState().activateTab(galleryTab); useTabs.getState().activateTab(galleryTab);
@@ -1024,7 +1010,7 @@ export default class Explorer {
tabKind: ViewModels.CollectionTabKind.Gallery, tabKind: ViewModels.CollectionTabKind.Gallery,
title, title,
tabPath: title, tabPath: title,
onLoadStartKey: null, onLoadStartKey: undefined,
isTabsContentExpanded: ko.observable(true), isTabsContentExpanded: ko.observable(true),
}, },
{ {
@@ -1070,8 +1056,9 @@ export default class Explorer {
const title = "Enable Notebooks (Preview)"; const title = "Enable Notebooks (Preview)";
const description = const description =
"You have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account."; "You have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
useSidePanel
this.openSetupNotebooksPanel(title, description); .getState()
.openSidePanel(title, <SetupNoteBooksPanel explorer={this} panelTitle={title} panelDescription={description} />);
} }
public async handleOpenFileAction(path: string): Promise<void> { public async handleOpenFileAction(path: string): Promise<void> {
@@ -1106,18 +1093,6 @@ export default class Explorer {
.openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />); .openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />);
} }
public openAddDatabasePane(): void {
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this} />);
}
public openBrowseQueriesPanel(): void {
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} />);
}
public openSaveQueryPanel(): void {
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this} />);
}
public openUploadFilePanel(parent?: NotebookContentItem): void { public openUploadFilePanel(parent?: NotebookContentItem): void {
parent = parent || this.resourceTree.myNotebooksContentRoot; parent = parent || this.resourceTree.myNotebooksContentRoot;
useSidePanel useSidePanel
@@ -1128,25 +1103,6 @@ export default class Explorer {
); );
} }
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
useSidePanel
.getState()
.openSidePanel(
header,
<GitHubReposPanel
explorer={this}
gitHubClientProp={this.notebookManager.gitHubClient}
junoClientProp={junoClient}
/>
);
}
public openSetupNotebooksPanel(title: string, description: string): void {
useSidePanel
.getState()
.openSidePanel(title, <SetupNoteBooksPanel explorer={this} panelTitle={title} panelDescription={description} />);
}
public async refreshExplorer(): Promise<void> { public async refreshExplorer(): Promise<void> {
userContext.authType === AuthType.ResourceToken userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()

View File

@@ -1,12 +1,11 @@
import React from "react";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import { GraphHighlightedNodeData, EditedProperties } from "./GraphExplorer"; import React from "react";
import { EditorNodePropertiesComponent, EditorNodePropertiesComponentProps } from "./EditorNodePropertiesComponent"; import { EditorNodePropertiesComponent, EditorNodePropertiesComponentProps } from "./EditorNodePropertiesComponent";
describe("<EditorNodePropertiesComponent />", () => { describe("<EditorNodePropertiesComponent />", () => {
// Tests that: single value prop is rendered with a textbox and a delete button // Tests that: single value prop is rendered with a textbox and a delete button
// multi-value prop only a delete button (cannot be edited) // multi-value prop only a delete button (cannot be edited)
const onUpdateProperties = jest.fn();
it("renders component", () => { it("renders component", () => {
const props: EditorNodePropertiesComponentProps = { const props: EditorNodePropertiesComponentProps = {
editedProperties: { editedProperties: {
@@ -24,7 +23,6 @@ describe("<EditorNodePropertiesComponent />", () => {
{ value: true, type: "boolean" }, { value: true, type: "boolean" },
{ value: false, type: "boolean" }, { value: false, type: "boolean" },
{ value: undefined, type: "null" }, { value: undefined, type: "null" },
{ value: null, type: "null" },
], ],
}, },
], ],
@@ -41,14 +39,13 @@ describe("<EditorNodePropertiesComponent />", () => {
{ value: true, type: "boolean" }, { value: true, type: "boolean" },
{ value: false, type: "boolean" }, { value: false, type: "boolean" },
{ value: undefined, type: "null" }, { value: undefined, type: "null" },
{ value: null, type: "null" },
], ],
}, },
], ],
addedProperties: [], addedProperties: [],
droppedKeys: [], droppedKeys: [],
}, },
onUpdateProperties: (editedProperties: EditedProperties): void => {}, onUpdateProperties,
}; };
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />); const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
@@ -81,7 +78,7 @@ describe("<EditorNodePropertiesComponent />", () => {
addedProperties: [], addedProperties: [],
droppedKeys: [], droppedKeys: [],
}, },
onUpdateProperties: (editedProperties: EditedProperties): void => {}, onUpdateProperties,
}; };
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />); const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

View File

@@ -4,12 +4,12 @@
*/ */
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { EditedProperties } from "./GraphExplorer";
import DeleteIcon from "../../../../images/delete.svg";
import AddIcon from "../../../../images/Add-property.svg"; import AddIcon from "../../../../images/Add-property.svg";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent"; import DeleteIcon from "../../../../images/delete.svg";
import * as ViewModels from "../../../Contracts/ViewModels";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement"; import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { EditedProperties } from "./GraphExplorer";
import { ReadOnlyNodePropertiesComponent } from "./ReadOnlyNodePropertiesComponent";
export interface EditorNodePropertiesComponentProps { export interface EditorNodePropertiesComponentProps {
editedProperties: EditedProperties; editedProperties: EditedProperties;
@@ -48,7 +48,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
const editedProperties = this.props.editedProperties; const editedProperties = this.props.editedProperties;
// search for it // search for it
for (let i = 0; i < editedProperties.existingProperties.length; i++) { for (let i = 0; i < editedProperties.existingProperties.length; i++) {
let ip = editedProperties.existingProperties[i]; const ip = editedProperties.existingProperties[i];
if (ip.key === key) { if (ip.key === key) {
editedProperties.existingProperties.splice(i, 1); editedProperties.existingProperties.splice(i, 1);
editedProperties.droppedKeys.push(key); editedProperties.droppedKeys.push(key);
@@ -60,7 +60,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
private removeAddedProperty(index: number): void { private removeAddedProperty(index: number): void {
const editedProperties = this.props.editedProperties; const editedProperties = this.props.editedProperties;
let ap = editedProperties.addedProperties; const ap = editedProperties.addedProperties;
ap.splice(index, 1); ap.splice(index, 1);
this.props.onUpdateProperties(editedProperties); this.props.onUpdateProperties(editedProperties);
@@ -68,7 +68,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
private addProperty(): void { private addProperty(): void {
const editedProperties = this.props.editedProperties; const editedProperties = this.props.editedProperties;
let ap = editedProperties.addedProperties; const ap = editedProperties.addedProperties;
ap.push({ key: "", values: [{ value: "", type: EditorNodePropertiesComponent.DEFAULT_PROPERTY_TYPE }] }); ap.push({ key: "", values: [{ value: "", type: EditorNodePropertiesComponent.DEFAULT_PROPERTY_TYPE }] });
this.props.onUpdateProperties(editedProperties); this.props.onUpdateProperties(editedProperties);
} }
@@ -126,7 +126,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
onChange={(e) => { onChange={(e) => {
singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString; singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
if (singleValue.type === "null") { if (singleValue.type === "null") {
singleValue.value = null; singleValue.value = undefined;
} }
this.props.onUpdateProperties(this.props.editedProperties); this.props.onUpdateProperties(this.props.editedProperties);
}} }}
@@ -144,7 +144,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns" className="rightPaneTrashIcon rightPaneBtns"
as="span" as="span"
aria-label="Delete property" aria-label="Delete property"
onActivated={(e) => this.removeExistingProperty(key)} onActivated={() => this.removeExistingProperty(key)}
> >
<img src={DeleteIcon} alt="Delete" /> <img src={DeleteIcon} alt="Delete" />
</AccessibleElement> </AccessibleElement>
@@ -166,7 +166,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns" className="rightPaneTrashIcon rightPaneBtns"
as="span" as="span"
aria-label="Remove existing property" aria-label="Remove existing property"
onActivated={(e) => this.removeExistingProperty(nodeProp.key)} onActivated={() => this.removeExistingProperty(nodeProp.key)}
> >
<img src={DeleteIcon} alt="Delete" /> <img src={DeleteIcon} alt="Delete" />
</AccessibleElement> </AccessibleElement>
@@ -206,7 +206,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
onChange={(e) => { onChange={(e) => {
firstValue.value = e.target.value; firstValue.value = e.target.value;
if (firstValue.type === "null") { if (firstValue.type === "null") {
firstValue.value = null; firstValue.value = undefined;
} }
this.props.onUpdateProperties(this.props.editedProperties); this.props.onUpdateProperties(this.props.editedProperties);
}} }}
@@ -235,7 +235,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns" className="rightPaneTrashIcon rightPaneBtns"
as="span" as="span"
aria-label="Remove property" aria-label="Remove property"
onActivated={(e) => this.removeAddedProperty(index)} onActivated={() => this.removeAddedProperty(index)}
> >
<img src={DeleteIcon} alt="Delete" /> <img src={DeleteIcon} alt="Delete" />
</AccessibleElement> </AccessibleElement>

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import CloseIcon from "../../../../images/close-black.svg"; import CloseIcon from "../../../../images/close-black.svg";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
export interface QueryContainerComponentProps { export interface QueryContainerComponentProps {
initialQuery: string; initialQuery: string;
@@ -82,7 +82,7 @@ export class QueryContainerComponent extends React.Component<
<button <button
type="button" type="button"
className="filterbtnstyle queryButton" className="filterbtnstyle queryButton"
onClick={(e) => this.props.onExecuteClick(this.state.query)} onClick={() => this.props.onExecuteClick(this.state.query)}
disabled={this.props.isLoading || !QueryContainerComponent.isQueryValid(this.state.query)} disabled={this.props.isLoading || !QueryContainerComponent.isQueryValid(this.state.query)}
> >
Execute Gremlin Query Execute Gremlin Query

View File

@@ -4,9 +4,9 @@
*/ */
import * as React from "react"; import * as React from "react";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { GraphHighlightedNodeData, NeighborVertexBasicInfo } from "./GraphExplorer"; import { GraphHighlightedNodeData, NeighborVertexBasicInfo } from "./GraphExplorer";
import * as GraphUtil from "./GraphUtil"; import * as GraphUtil from "./GraphUtil";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
export interface ReadOnlyNeighborsComponentProps { export interface ReadOnlyNeighborsComponentProps {
node: GraphHighlightedNodeData; node: GraphHighlightedNodeData;
@@ -48,7 +48,7 @@ export class ReadOnlyNeighborsComponent extends React.Component<ReadOnlyNeighbor
className="clickableLink" className="clickableLink"
as="a" as="a"
aria-label={_neighbor.name} aria-label={_neighbor.name}
onActivated={(e) => this.props.selectNode(_neighbor.id)} onActivated={() => this.props.selectNode(_neighbor.id)}
title={GraphUtil.getNeighborTitle(_neighbor)} title={GraphUtil.getNeighborTitle(_neighbor)}
> >
{_neighbor.name} {_neighbor.name}

View File

@@ -4,8 +4,8 @@
*/ */
import * as React from "react"; import * as React from "react";
import { GraphHighlightedNodeData } from "./GraphExplorer";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { GraphHighlightedNodeData } from "./GraphExplorer";
export interface ReadOnlyNodePropertiesComponentProps { export interface ReadOnlyNodePropertiesComponentProps {
node: GraphHighlightedNodeData; node: GraphHighlightedNodeData;

View File

@@ -37,7 +37,7 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
</td> </td>
<td <td
className="valueCol" className="valueCol"
title="efgh, 1234, true, false, undefined, null" title="efgh, 1234, true, false, undefined"
> >
<div <div
className="propertyValue" className="propertyValue"
@@ -69,12 +69,6 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
> >
undefined undefined
</div> </div>
<div
className="propertyValue isNull"
key="null"
>
null
</div>
</td> </td>
</tr> </tr>
<tr <tr
@@ -178,12 +172,6 @@ exports[`<EditorNodePropertiesComponent /> renders component 1`] = `
> >
undefined undefined
</div> </div>
<div
className="propertyValue isNull"
key="null"
>
null
</div>
</td> </td>
<td /> <td />
<td <td

View File

@@ -256,7 +256,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
}, },
} as DatabaseAccount, } as DatabaseAccount,
}); });
console.log(mockExplorer);
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeUndefined(); expect(openCassandraShellBtn).toBeUndefined();

View File

@@ -22,6 +22,7 @@ import * as Constants from "../../../Common/Constants";
import { configContext, Platform } from "../../../ConfigContext"; import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { useSidePanel } from "../../../hooks/useSidePanel";
import { JunoClient } from "../../../Juno/JunoClient";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils"; import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
@@ -30,8 +31,12 @@ import { CommandButtonComponentProps } from "../../Controls/CommandButton/Comman
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook"; import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen"; 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 { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane"; import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane";
import { SetupNoteBooksPanel } from "../../Panes/SetupNotebooksPanel/SetupNotebooksPanel";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { SelectedNodeState } from "../../useSelectedNode"; import { SelectedNodeState } from "../../useSelectedNode";
@@ -281,9 +286,8 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
return { return {
iconSrc: AddDatabaseIcon, iconSrc: AddDatabaseIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => { onCommandClick: () =>
container.openAddDatabasePane(); useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
},
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
@@ -415,7 +419,8 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
return { return {
iconSrc: BrowseQueriesIcon, iconSrc: BrowseQueriesIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openBrowseQueriesPanel(), onCommandClick: () =>
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
@@ -448,7 +453,13 @@ function createEnableNotebooksButton(container: Explorer): CommandButtonComponen
return { return {
iconSrc: EnableNotebooksIcon, iconSrc: EnableNotebooksIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openSetupNotebooksPanel(label, description), onCommandClick: () =>
useSidePanel
.getState()
.openSidePanel(
label,
<SetupNoteBooksPanel explorer={container} panelTitle={label} panelDescription={description} />
),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: !useNotebook.getState().isNotebooksEnabledForAccount, disabled: !useNotebook.getState().isNotebooksEnabledForAccount,
@@ -486,7 +497,12 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
if (useNotebook.getState().isNotebookEnabled) { if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
} else { } else {
container.openSetupNotebooksPanel(title, description); useSidePanel
.getState()
.openSidePanel(
title,
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
);
} }
}, },
commandButtonLabel: label, commandButtonLabel: label,
@@ -513,7 +529,12 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
if (useNotebook.getState().isNotebookEnabled) { if (useNotebook.getState().isNotebookEnabled) {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
} else { } else {
container.openSetupNotebooksPanel(title, description); useSidePanel
.getState()
.openSidePanel(
title,
<SetupNoteBooksPanel explorer={container} panelTitle={title} panelDescription={description} />
);
} }
}, },
commandButtonLabel: label, commandButtonLabel: label,
@@ -540,10 +561,21 @@ function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonC
function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps {
const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
const junoClient = new JunoClient();
return { return {
iconSrc: GitHubIcon, iconSrc: GitHubIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.openGitHubReposPanel(label), onCommandClick: () =>
useSidePanel
.getState()
.openSidePanel(
label,
<GitHubReposPanel
explorer={container}
gitHubClientProp={container.notebookManager.gitHubClient}
junoClientProp={junoClient}
/>
),
commandButtonLabel: label, commandButtonLabel: label,
hasPopup: false, hasPopup: false,
disabled: false, disabled: false,

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { AppState, ContentRef, selectors } from "@nteract/core"; import { AppState, ContentRef, selectors } from "@nteract/core";
import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import * as NteractUtil from "../NTeractUtil"; import * as NteractUtil from "../NTeractUtil";

View File

@@ -2,7 +2,6 @@ import { AppState, ContentRef, selectors } from "@nteract/core";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import styled from "styled-components"; import styled from "styled-components";
import NotebookRenderer from "../../../NotebookRenderer/NotebookRenderer"; import NotebookRenderer from "../../../NotebookRenderer/NotebookRenderer";
import * as TextFile from "./text-file"; import * as TextFile from "./text-file";
@@ -32,14 +31,14 @@ interface FileProps {
export class File extends React.PureComponent<FileProps> { export class File extends React.PureComponent<FileProps> {
getChoice = () => { getChoice = () => {
let choice = null; let choice;
// notebooks don't report a mimetype so we'll use the content.type // notebooks don't report a mimetype so we'll use the content.type
if (this.props.type === "notebook") { if (this.props.type === "notebook") {
choice = <NotebookRenderer contentRef={this.props.contentRef} />; choice = <NotebookRenderer contentRef={this.props.contentRef} />;
} else if (this.props.type === "dummy") { } else if (this.props.type === "dummy") {
choice = null; choice = undefined;
} else if (this.props.mimetype == null || !TextFile.handles(this.props.mimetype)) { } else if (this.props.mimetype === undefined || !TextFile.handles(this.props.mimetype)) {
// This should not happen as we intercept mimetype upstream, but just in case // This should not happen as we intercept mimetype upstream, but just in case
choice = ( choice = (
<PaddedContainer> <PaddedContainer>

View File

@@ -1,10 +1,10 @@
import * as StringUtils from "../../../../../Utils/StringUtils";
import { actions, AppState, ContentRef, selectors } from "@nteract/core"; import { actions, AppState, ContentRef, selectors } from "@nteract/core";
import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor"; import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import styled from "styled-components"; import styled from "styled-components";
import * as StringUtils from "../../../../../Utils/StringUtils";
const EditorContainer = styled.div` const EditorContainer = styled.div`
position: absolute; position: absolute;
@@ -37,7 +37,7 @@ interface TextFileState {
class EditorPlaceholder extends React.PureComponent<MonacoEditorProps> { class EditorPlaceholder extends React.PureComponent<MonacoEditorProps> {
render(): JSX.Element { render(): JSX.Element {
// TODO: Show a little blocky placeholder // TODO: Show a little blocky placeholder
return null; return undefined;
} }
} }
@@ -98,7 +98,7 @@ function makeMapStateToTextFileProps(
return { return {
contentRef, contentRef,
mimetype: content.mimetype != null ? content.mimetype : "text/plain", mimetype: content.mimetype !== undefined ? content.mimetype : "text/plain",
text, text,
}; };
}; };

View File

@@ -20,6 +20,7 @@ import { userContext } from "../../UserContext";
import { getFullName } from "../../Utils/UserUtils"; import { getFullName } from "../../Utils/UserUtils";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane"; import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane"; import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider"; import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider";
@@ -88,7 +89,18 @@ export default class NotebookManager {
this.gitHubClient.setToken(token?.access_token); this.gitHubClient.setToken(token?.access_token);
if (this?.gitHubOAuthService.isLoggedIn()) { if (this?.gitHubOAuthService.isLoggedIn()) {
useSidePanel.getState().closeSidePanel(); useSidePanel.getState().closeSidePanel();
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient); setTimeout(() => {
useSidePanel
.getState()
.openSidePanel(
"Manage GitHub settings",
<GitHubReposPanel
explorer={this.params.container}
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
junoClientProp={this.junoClient}
/>
);
}, 200);
} }
this.params.refreshCommandBarButtons(); this.params.refreshCommandBarButtons();
@@ -161,7 +173,17 @@ export default class NotebookManager {
undefined, undefined,
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.", "Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
"Connect to GitHub", "Connect to GitHub",
() => this.params.container.openGitHubReposPanel("Connect to GitHub"), () =>
useSidePanel
.getState()
.openSidePanel(
"Connect to GitHub",
<GitHubReposPanel
explorer={this.params.container}
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
junoClientProp={this.junoClient}
/>
),
"Cancel", "Cancel",
undefined undefined
); );

View File

@@ -5,7 +5,7 @@ import "./Prompt.less";
export const promptContent = (props: PassedPromptProps): JSX.Element => { export const promptContent = (props: PassedPromptProps): JSX.Element => {
if (props.status === "busy") { if (props.status === "busy") {
const stopButtonText: string = "Stop cell execution"; const stopButtonText = "Stop cell execution";
return ( return (
<div <div
style={{ position: "sticky", width: "100%", maxHeight: "100%", left: 0, top: 0, zIndex: 300 }} 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> </div>
); );
} else if (props.isHovered) { } else if (props.isHovered) {
const playButtonText: string = "Run cell"; const playButtonText = "Run cell";
return ( return (
<IconButton <IconButton
className="runCellButton" className="runCellButton"

View File

@@ -1,6 +1,5 @@
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { StatusBar } from "./StatusBar"; import { StatusBar } from "./StatusBar";
describe("StatusBar", () => { describe("StatusBar", () => {
@@ -28,8 +27,8 @@ describe("StatusBar", () => {
kernelSpecDisplayName: "javascript", kernelSpecDisplayName: "javascript",
kernelStatus: "kernelStatus", kernelStatus: "kernelStatus",
}, },
null, undefined,
null undefined
); );
expect(shouldUpdate).toBe(true); expect(shouldUpdate).toBe(true);
}); });
@@ -47,8 +46,8 @@ describe("StatusBar", () => {
kernelSpecDisplayName: "python3", kernelSpecDisplayName: "python3",
kernelStatus: "kernelStatus", kernelStatus: "kernelStatus",
}, },
null, undefined,
null undefined
); );
expect(shouldUpdate).toBe(true); expect(shouldUpdate).toBe(true);
}); });

View File

@@ -2,6 +2,7 @@ import { AppState, ContentRef, selectors } from "@nteract/core";
import distanceInWordsToNow from "date-fns/distance_in_words_to_now"; import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import styled from "styled-components";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
interface Props { interface Props {
@@ -12,8 +13,6 @@ interface Props {
const NOT_CONNECTED = "not connected"; const NOT_CONNECTED = "not connected";
import styled from "styled-components";
export const LeftStatus = styled.div` export const LeftStatus = styled.div`
float: left; float: left;
display: block; display: block;
@@ -80,7 +79,7 @@ interface InitialProps {
contentRef: ContentRef; contentRef: ContentRef;
} }
const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => { const makeMapStateToProps = (_initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => {
const { contentRef } = initialProps; const { contentRef } = initialProps;
const mapStateToProps = (state: AppState) => { const mapStateToProps = (state: AppState) => {
@@ -90,26 +89,26 @@ const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps)
return { return {
kernelStatus: NOT_CONNECTED, kernelStatus: NOT_CONNECTED,
kernelSpecDisplayName: "no kernel", kernelSpecDisplayName: "no kernel",
lastSaved: null, lastSaved: undefined,
}; };
} }
const kernelRef = content.model.kernelRef; const kernelRef = content.model.kernelRef;
let kernel = null; let kernel;
if (kernelRef) { if (kernelRef) {
kernel = selectors.kernel(state, { kernelRef }); kernel = selectors.kernel(state, { kernelRef });
} }
const lastSaved = content && content.lastSaved ? content.lastSaved : null; const lastSaved = content && content.lastSaved ? content.lastSaved : undefined;
const kernelStatus = kernel != null && kernel.status != null ? kernel.status : NOT_CONNECTED; const kernelStatus = kernel?.status || NOT_CONNECTED;
// TODO: We need kernels associated to the kernelspec they came from // TODO: We need kernels associated to the kernelspec they came from
// so we can pluck off the display_name and provide it here // so we can pluck off the display_name and provide it here
let kernelSpecDisplayName = " "; let kernelSpecDisplayName = " ";
if (kernelStatus === NOT_CONNECTED) { if (kernelStatus === NOT_CONNECTED) {
kernelSpecDisplayName = "no kernel"; kernelSpecDisplayName = "no kernel";
} else if (kernel != null && kernel.kernelSpecName != null) { } else if (kernel?.kernelSpecName) {
kernelSpecDisplayName = kernel.kernelSpecName; kernelSpecDisplayName = kernel.kernelSpecName;
} else if (content && content.type === "notebook") { } else if (content && content.type === "notebook") {
kernelSpecDisplayName = selectors.notebook.displayName(content.model) || " "; kernelSpecDisplayName = selectors.notebook.displayName(content.model) || " ";

View File

@@ -27,7 +27,7 @@ interface DispatchProps {
moveCell: (destinationId: CellId, above: boolean) => void; moveCell: (destinationId: CellId, above: boolean) => void;
clearOutputs: () => void; clearOutputs: () => void;
deleteCell: () => void; deleteCell: () => void;
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => void; traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) => void;
takeNotebookSnapshot: (payload: SnapshotRequest) => void; takeNotebookSnapshot: (payload: SnapshotRequest) => void;
} }
@@ -203,7 +203,7 @@ const mapDispatchToProps = (
dispatch(actions.moveCell({ id, contentRef, destinationId, above })), dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })), clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })), deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: string) =>
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })), dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data })),
takeNotebookSnapshot: (request: SnapshotRequest) => dispatch(cdbActions.takeNotebookSnapshot(request)), takeNotebookSnapshot: (request: SnapshotRequest) => dispatch(cdbActions.takeNotebookSnapshot(request)),
}); });

View File

@@ -1,8 +1,7 @@
import { ContentRef } from "@nteract/core";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import { ContentRef } from "@nteract/core";
import * as actions from "../../NotebookComponent/actions"; import * as actions from "../../NotebookComponent/actions";
interface ComponentProps { interface ComponentProps {
@@ -29,10 +28,7 @@ class HoverableCell extends React.Component<ComponentProps & DispatchProps> {
} }
} }
const mapDispatchToProps = ( const mapDispatchToProps = (dispatch: Dispatch, { id }: { id: string }): DispatchProps => ({
dispatch: Dispatch,
{ id, contentRef }: { id: string; contentRef: ContentRef }
): DispatchProps => ({
hover: () => dispatch(actions.setHoveredCell({ cellId: id })), hover: () => dispatch(actions.setHoveredCell({ cellId: id })),
unHover: () => dispatch(actions.setHoveredCell({ cellId: undefined })), unHover: () => dispatch(actions.setHoveredCell({ cellId: undefined })),
}); });

View File

@@ -113,7 +113,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
collectionId: "", collectionId: "",
enableIndexing: true, enableIndexing: true,
isSharded: userContext.apiType !== "Tables", isSharded: userContext.apiType !== "Tables",
partitionKey: "", partitionKey:
(userContext.features.partitionKeyDefault && userContext.apiType === "SQL") ||
(userContext.features.partitionKeyDefault && userContext.apiType === "Mongo")
? "/id"
: "",
enableDedicatedThroughput: false, enableDedicatedThroughput: false,
createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"), createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"),
useHashV2: false, useHashV2: false,
@@ -413,6 +417,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</TooltipHost> </TooltipHost>
</Stack> </Stack>
<Text variant="small" aria-label="pkDescription">
{this.getPartitionKeySubtext()}
</Text>
<input <input
type="text" type="text"
id="addCollection-partitionKeyValue" id="addCollection-partitionKeyValue"
@@ -807,6 +815,17 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return tooltipText; 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 { private getAnalyticalStorageTooltipContent(): JSX.Element {
return ( return (
<Text variant="small"> <Text variant="small">

View File

@@ -120,6 +120,7 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos"); handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
} }
} }
useSidePanel.getState().closeSidePanel();
} }
public resetData(): void { public resetData(): void {
@@ -144,11 +145,18 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
private setup(forceShowConnectToGitHub = false): void { private setup(forceShowConnectToGitHub = false): void {
forceShowConnectToGitHub || !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn() forceShowConnectToGitHub || !this.props.explorer.notebookManager?.gitHubOAuthService.isLoggedIn()
? this.setupForConnectToGitHub() ? this.setupForConnectToGitHub(forceShowConnectToGitHub)
: this.setupForManageRepos(); : this.setupForManageRepos();
} }
private setupForConnectToGitHub(): void { private setupForConnectToGitHub(forceShowConnectToGitHub: boolean): void {
if (forceShowConnectToGitHub) {
const newState = { ...this.state.gitHubReposState };
newState.showAuthorizeAccess = forceShowConnectToGitHub;
this.setState({
gitHubReposState: newState,
});
}
this.setState({ this.setState({
isExecuting: false, isExecuting: false,
}); });
@@ -368,46 +376,28 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
isLoading: true, isLoading: true,
loadMore: (): Promise<void> => this.loadMoreBranches(item.repo), 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); 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; this.isAddedRepo = false;
} }

View File

@@ -31,6 +31,15 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"gitHubOAuthService": GitHubOAuthService {
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"token": [Function],
},
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {

View File

@@ -4,8 +4,8 @@ import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { useSidePanel } from "../../hooks/useSidePanel"; import { useSidePanel } from "../../hooks/useSidePanel";
export interface PanelContainerProps { export interface PanelContainerProps {
headerText: string; headerText?: string;
panelContent: JSX.Element; panelContent?: JSX.Element;
isConsoleExpanded: boolean; isConsoleExpanded: boolean;
isOpen: boolean; isOpen: boolean;
panelWidth?: string; panelWidth?: string;
@@ -66,8 +66,8 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
); );
} }
private onDissmiss = (ev?: React.SyntheticEvent<HTMLElement>): void => { private onDissmiss = (ev?: KeyboardEvent | React.SyntheticEvent<HTMLElement>): void => {
if ((ev.target as HTMLElement).id === "notificationConsoleHeader") { if (ev && (ev.target as HTMLElement).id === "notificationConsoleHeader") {
ev.preventDefault(); ev.preventDefault();
} else { } else {
useSidePanel.getState().closeSidePanel(); useSidePanel.getState().closeSidePanel();

View File

@@ -21,6 +21,15 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"resourceTree": ResourceTreeAdapter { "resourceTree": ResourceTreeAdapter {
"container": [Circular], "container": [Circular],
"copyNotebook": [Function], "copyNotebook": [Function],
"gitHubOAuthService": GitHubOAuthService {
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"token": [Function],
},
"junoClient": JunoClient {
"cachedPinnedRepos": [Function],
},
"parameters": [Function], "parameters": [Function],
}, },
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken { "resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {

View File

@@ -16,6 +16,7 @@ import CollectionIcon from "../../../images/tree-collection.svg";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { useSidePanel } from "../../hooks/useSidePanel";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getCollectionName, getDatabaseName } from "../../Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "../../Utils/APITypeUtils";
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher"; import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
@@ -23,6 +24,8 @@ import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity"; import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";
import { AddDatabasePanel } from "../Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
@@ -173,7 +176,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
</li> </li>
))} ))}
<li> <li>
<a role="link" href={SplashScreen.seeMoreItemUrl} target="_blank" tabIndex={0}> <a role="link" href={SplashScreen.seeMoreItemUrl} rel="noreferrer" target="_blank" tabIndex={0}>
{SplashScreen.seeMoreItemTitle} {SplashScreen.seeMoreItemTitle}
</a> </a>
</li> </li>
@@ -241,20 +244,20 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
iconSrc: NewQueryIcon, iconSrc: NewQueryIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null); selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined);
}, },
title: "New SQL Query", title: "New SQL Query",
description: null, description: undefined,
}); });
} else if (userContext.apiType === "Mongo") { } else if (userContext.apiType === "Mongo") {
items.push({ items.push({
iconSrc: NewQueryIcon, iconSrc: NewQueryIcon,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null); selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
}, },
title: "New Query", title: "New Query",
description: null, description: undefined,
}); });
} }
@@ -262,8 +265,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
items.push({ items.push({
iconSrc: OpenQueryIcon, iconSrc: OpenQueryIcon,
title: "Open Query", title: "Open Query",
description: null, description: undefined,
onClick: () => this.container.openBrowseQueriesPanel(), onClick: () =>
useSidePanel
.getState()
.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.container} />),
}); });
} }
@@ -271,10 +277,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
items.push({ items.push({
iconSrc: NewStoredProcedureIcon, iconSrc: NewStoredProcedureIcon,
title: "New Stored Procedure", title: "New Stored Procedure",
description: null, description: undefined,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
}, },
}); });
} }
@@ -286,7 +292,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
items.push({ items.push({
iconSrc: ScaleAndSettingsIcon, iconSrc: ScaleAndSettingsIcon,
title: label, title: label,
description: null, description: undefined,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onSettingsClick(); selectedCollection && selectedCollection.onSettingsClick();
@@ -296,8 +302,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
items.push({ items.push({
iconSrc: AddDatabaseIcon, iconSrc: AddDatabaseIcon,
title: "New " + getDatabaseName(), title: "New " + getDatabaseName(),
description: null, description: undefined,
onClick: () => this.container.openAddDatabasePane(), onClick: () =>
useSidePanel
.getState()
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
}); });
} }
@@ -348,19 +357,19 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
private createTipsItems(): SplashScreenItem[] { private createTipsItems(): SplashScreenItem[] {
return [ return [
{ {
iconSrc: null, iconSrc: undefined,
title: "Data Modeling", title: "Data Modeling",
description: "Learn more about modeling", description: "Learn more about modeling",
onClick: () => window.open(SplashScreen.dataModelingUrl), onClick: () => window.open(SplashScreen.dataModelingUrl),
}, },
{ {
iconSrc: null, iconSrc: undefined,
title: "Cost & Throughput Calculation", title: "Cost & Throughput Calculation",
description: "Learn more about cost calculation", description: "Learn more about cost calculation",
onClick: () => window.open(SplashScreen.throughputEstimatorUrl), onClick: () => window.open(SplashScreen.throughputEstimatorUrl),
}, },
{ {
iconSrc: null, iconSrc: undefined,
title: "Configure automatic failover", title: "Configure automatic failover",
description: "Learn more about Cosmos DB high-availability", description: "Learn more about Cosmos DB high-availability",
onClick: () => window.open(SplashScreen.failoverUrl), onClick: () => window.open(SplashScreen.failoverUrl),

View File

@@ -1,4 +1,5 @@
export function getQuotedCqlIdentifier(identifier: string): string { // Added return type optional undefined because passing undefined from test cases.
export function getQuotedCqlIdentifier(identifier: string | undefined): string | undefined {
let result = identifier; let result = identifier;
if (!identifier) { if (!identifier) {
return result; return result;

View File

@@ -22,12 +22,15 @@ import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole"; import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import * as QueryUtils from "../../../Utils/QueryUtils"; import * as QueryUtils from "../../../Utils/QueryUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane";
import TabsBase from "../TabsBase"; import TabsBase from "../TabsBase";
import "./QueryTabComponent.less"; import "./QueryTabComponent.less";
@@ -389,13 +392,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
}; };
public onSaveQueryClick = (): void => { public onSaveQueryClick = (): void => {
this.props.collection && this.props.collection.container && this.props.collection.container.openSaveQueryPanel(); useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
}; };
public onSavedQueriesClick = (): void => { public onSavedQueriesClick = (): void => {
this.props.collection && useSidePanel
this.props.collection.container && .getState()
this.props.collection.container.openBrowseQueriesPanel(); .openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.props.collection.container} />);
}; };
public async onFetchNextPageClick(): Promise<void> { public async onFetchNextPageClick(): Promise<void> {

View File

@@ -4,18 +4,12 @@ import Collection from "./Collection";
jest.mock("monaco-editor"); jest.mock("monaco-editor");
describe("Collection", () => { describe("Collection", () => {
function generateCollection( const generateCollection = (container: Explorer, databaseId: string, data: DataModels.Collection): Collection =>
container: Explorer, new Collection(container, databaseId, data);
databaseId: string,
data: DataModels.Collection,
offer: DataModels.Offer
): Collection {
return new Collection(container, databaseId, data);
}
function generateMockCollectionsDataModelWithPartitionKey( const generateMockCollectionsDataModelWithPartitionKey = (
partitionKey: DataModels.PartitionKey partitionKey: DataModels.PartitionKey
): DataModels.Collection { ): DataModels.Collection => {
return { return {
defaultTtl: 1, defaultTtl: 1,
indexingPolicy: {} as DataModels.IndexingPolicy, indexingPolicy: {} as DataModels.IndexingPolicy,
@@ -26,13 +20,12 @@ describe("Collection", () => {
_ts: 1, _ts: 1,
id: "", id: "",
}; };
} };
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection { const generateMockCollectionWithDataModel = (data: DataModels.Collection): Collection => {
const mockContainer = {} as Explorer; const mockContainer = {} as Explorer;
return generateCollection(mockContainer, "abc", data);
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer); };
}
describe("Partition key path parsing", () => { describe("Partition key path parsing", () => {
let collection: Collection; let collection: Collection;
@@ -88,7 +81,7 @@ describe("Collection", () => {
kind: "Hash", kind: "Hash",
}); });
collection = generateMockCollectionWithDataModel(collectionsDataModel); collection = generateMockCollectionWithDataModel(collectionsDataModel);
expect(collection.partitionKeyPropertyHeader).toBeNull; expect(collection.partitionKeyPropertyHeader).toBeNull();
}); });
}); });
}); });

View File

@@ -744,8 +744,8 @@ export default class Collection implements ViewModels.Collection {
StoredProcedure.create(source, event); StoredProcedure.create(source, event);
} }
public onNewUserDefinedFunctionClick(source: ViewModels.Collection, event: MouseEvent) { public onNewUserDefinedFunctionClick(source: ViewModels.Collection) {
UserDefinedFunction.create(source, event); UserDefinedFunction.create(source);
} }
public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) { public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) {

View File

@@ -16,8 +16,10 @@ import { Areas } from "../../Common/Constants";
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
import { useSidePanel } from "../../hooks/useSidePanel";
import { useTabs } from "../../hooks/useTabs"; import { useTabs } from "../../hooks/useTabs";
import { IPinnedRepo } from "../../Juno/JunoClient"; import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -33,6 +35,7 @@ import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil"; import { NotebookUtil } from "../Notebook/NotebookUtil";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import TabsBase from "../Tabs/TabsBase"; import TabsBase from "../Tabs/TabsBase";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
@@ -53,6 +56,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
public galleryContentRoot: NotebookContentItem; public galleryContentRoot: NotebookContentItem;
public myNotebooksContentRoot: NotebookContentItem; public myNotebooksContentRoot: NotebookContentItem;
public gitHubNotebooksContentRoot: NotebookContentItem; public gitHubNotebooksContentRoot: NotebookContentItem;
public junoClient: JunoClient;
public gitHubOAuthService: GitHubOAuthService;
public constructor(private container: Explorer) { public constructor(private container: Explorer) {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
@@ -69,6 +74,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
useDatabases.subscribe(() => this.triggerRender()); useDatabases.subscribe(() => this.triggerRender());
this.triggerRender(); this.triggerRender();
this.junoClient = new JunoClient();
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
} }
private traceMyNotebookTreeInfo() { private traceMyNotebookTreeInfo() {
@@ -624,7 +631,17 @@ export class ResourceTreeAdapter implements ReactAdapter {
gitHubNotebooksTree.contextMenu = [ gitHubNotebooksTree.contextMenu = [
{ {
label: "Manage GitHub settings", label: "Manage GitHub settings",
onClick: () => this.container.openGitHubReposPanel("Manage GitHub settings"), onClick: () =>
useSidePanel
.getState()
.openSidePanel(
"Manage GitHub settings",
<GitHubReposPanel
explorer={this.container}
gitHubClientProp={this.container.notebookManager.gitHubClient}
junoClientProp={this.junoClient}
/>
),
}, },
{ {
label: "Disconnect from GitHub", label: "Disconnect from GitHub",

View File

@@ -30,7 +30,7 @@ export default class UserDefinedFunction {
this.body = ko.observable(data.body as string); this.body = ko.observable(data.body as string);
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { public static create(source: ViewModels.Collection) {
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1; const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
const userDefinedFunction = { const userDefinedFunction = {
id: "", id: "",
@@ -104,7 +104,9 @@ export default class UserDefinedFunction {
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid); useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
(reason) => {} () => {
/**/
}
); );
} }
} }

View File

@@ -12,7 +12,7 @@ window.addEventListener("load", async () => {
if (openerWindow) { if (openerWindow) {
const params = new URLSearchParams(document.location.search); const params = new URLSearchParams(document.location.search);
await postRobot.send( await postRobot.send(
window, openerWindow,
GitHubConnectorMsgType, GitHubConnectorMsgType,
{ {
state: params.get("state"), state: params.get("state"),

View File

@@ -1,11 +1,13 @@
import { IContent } from "@nteract/core"; import { IContent } from "@nteract/core";
import { fixture } from "@nteract/fixtures"; import { fixture } from "@nteract/fixtures";
import { HttpStatusCodes } from "../Common/Constants"; import { HttpStatusCodes } from "../Common/Constants";
import * as GitHubUtils from "../Utils/GitHubUtils";
import { GitHubClient, IGitHubCommit, IGitHubFile } from "./GitHubClient"; import { GitHubClient, IGitHubCommit, IGitHubFile } from "./GitHubClient";
import { GitHubContentProvider } from "./GitHubContentProvider"; import { GitHubContentProvider } from "./GitHubContentProvider";
import * as GitHubUtils from "../Utils/GitHubUtils";
const gitHubClient = new GitHubClient(() => {}); const gitHubClient = new GitHubClient(() => {
/**/
});
const gitHubContentProvider = new GitHubContentProvider({ const gitHubContentProvider = new GitHubContentProvider({
gitHubClient, gitHubClient,
promptForCommitMsg: () => Promise.resolve("commit msg"), promptForCommitMsg: () => Promise.resolve("commit msg"),
@@ -46,7 +48,7 @@ const sampleNotebookModel: IContent<"notebook"> = {
created: "", created: "",
last_modified: "date", last_modified: "date",
mimetype: "application/x-ipynb+json", mimetype: "application/x-ipynb+json",
content: sampleFile.content ? JSON.parse(sampleFile.content) : null, content: sampleFile.content ? JSON.parse(sampleFile.content) : undefined,
format: "json", format: "json",
}; };
@@ -54,7 +56,7 @@ describe("GitHubContentProvider remove", () => {
it("errors on invalid path", async () => { it("errors on invalid path", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync"); spyOn(GitHubClient.prototype, "getContentsAsync");
const response = await gitHubContentProvider.remove(null, "invalid path").toPromise(); const response = await gitHubContentProvider.remove(undefined, "invalid path").toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
expect(gitHubClient.getContentsAsync).not.toBeCalled(); expect(gitHubClient.getContentsAsync).not.toBeCalled();
@@ -63,7 +65,7 @@ describe("GitHubContentProvider remove", () => {
it("errors on failed read", async () => { it("errors on failed read", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.remove(null, sampleGitHubUri).toPromise(); const response = await gitHubContentProvider.remove(undefined, sampleGitHubUri).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -75,7 +77,7 @@ describe("GitHubContentProvider remove", () => {
); );
spyOn(GitHubClient.prototype, "deleteFileAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "deleteFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.remove(null, sampleGitHubUri).toPromise(); const response = await gitHubContentProvider.remove(undefined, sampleGitHubUri).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -90,7 +92,7 @@ describe("GitHubContentProvider remove", () => {
Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit }) Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit })
); );
const response = await gitHubContentProvider.remove(null, sampleGitHubUri).toPromise(); const response = await gitHubContentProvider.remove(undefined, sampleGitHubUri).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(HttpStatusCodes.NoContent); expect(response.status).toBe(HttpStatusCodes.NoContent);
expect(gitHubClient.deleteFileAsync).toBeCalled(); expect(gitHubClient.deleteFileAsync).toBeCalled();
@@ -102,7 +104,7 @@ describe("GitHubContentProvider get", () => {
it("errors on invalid path", async () => { it("errors on invalid path", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync"); spyOn(GitHubClient.prototype, "getContentsAsync");
const response = await gitHubContentProvider.get(null, "invalid path", null).toPromise(); const response = await gitHubContentProvider.get(undefined, "invalid path", undefined).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
expect(gitHubClient.getContentsAsync).not.toBeCalled(); expect(gitHubClient.getContentsAsync).not.toBeCalled();
@@ -111,7 +113,7 @@ describe("GitHubContentProvider get", () => {
it("errors on failed read", async () => { it("errors on failed read", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.get(null, sampleGitHubUri, null).toPromise(); const response = await gitHubContentProvider.get(undefined, sampleGitHubUri, undefined).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -122,7 +124,7 @@ describe("GitHubContentProvider get", () => {
Promise.resolve({ status: HttpStatusCodes.OK, data: sampleFile }) Promise.resolve({ status: HttpStatusCodes.OK, data: sampleFile })
); );
const response = await gitHubContentProvider.get(null, sampleGitHubUri, {}).toPromise(); const response = await gitHubContentProvider.get(undefined, sampleGitHubUri, {}).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(HttpStatusCodes.OK); expect(response.status).toBe(HttpStatusCodes.OK);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -134,7 +136,7 @@ describe("GitHubContentProvider update", () => {
it("errors on invalid path", async () => { it("errors on invalid path", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync"); spyOn(GitHubClient.prototype, "getContentsAsync");
const response = await gitHubContentProvider.update(null, "invalid path", null).toPromise(); const response = await gitHubContentProvider.update(undefined, "invalid path", undefined).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
expect(gitHubClient.getContentsAsync).not.toBeCalled(); expect(gitHubClient.getContentsAsync).not.toBeCalled();
@@ -143,7 +145,7 @@ describe("GitHubContentProvider update", () => {
it("errors on failed read", async () => { it("errors on failed read", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.update(null, sampleGitHubUri, null).toPromise(); const response = await gitHubContentProvider.update(undefined, sampleGitHubUri, undefined).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -155,7 +157,7 @@ describe("GitHubContentProvider update", () => {
); );
spyOn(GitHubClient.prototype, "renameFileAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "renameFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.update(null, sampleGitHubUri, sampleNotebookModel).toPromise(); const response = await gitHubContentProvider.update(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -170,7 +172,7 @@ describe("GitHubContentProvider update", () => {
Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit }) Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit })
); );
const response = await gitHubContentProvider.update(null, sampleGitHubUri, sampleNotebookModel).toPromise(); const response = await gitHubContentProvider.update(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(HttpStatusCodes.OK); expect(response.status).toBe(HttpStatusCodes.OK);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -186,7 +188,7 @@ describe("GitHubContentProvider create", () => {
it("errors on invalid path", async () => { it("errors on invalid path", async () => {
spyOn(GitHubClient.prototype, "createOrUpdateFileAsync"); spyOn(GitHubClient.prototype, "createOrUpdateFileAsync");
const response = await gitHubContentProvider.create(null, "invalid path", sampleNotebookModel).toPromise(); const response = await gitHubContentProvider.create(undefined, "invalid path", sampleNotebookModel).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
expect(gitHubClient.createOrUpdateFileAsync).not.toBeCalled(); expect(gitHubClient.createOrUpdateFileAsync).not.toBeCalled();
@@ -195,7 +197,7 @@ describe("GitHubContentProvider create", () => {
it("errors on failed create", async () => { it("errors on failed create", async () => {
spyOn(GitHubClient.prototype, "createOrUpdateFileAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "createOrUpdateFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.create(null, sampleGitHubUri, sampleNotebookModel).toPromise(); const response = await gitHubContentProvider.create(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.createOrUpdateFileAsync).toBeCalled(); expect(gitHubClient.createOrUpdateFileAsync).toBeCalled();
@@ -206,7 +208,7 @@ describe("GitHubContentProvider create", () => {
Promise.resolve({ status: HttpStatusCodes.Created, data: gitHubCommit }) Promise.resolve({ status: HttpStatusCodes.Created, data: gitHubCommit })
); );
const response = await gitHubContentProvider.create(null, sampleGitHubUri, sampleNotebookModel).toPromise(); const response = await gitHubContentProvider.create(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(HttpStatusCodes.Created); expect(response.status).toBe(HttpStatusCodes.Created);
expect(gitHubClient.createOrUpdateFileAsync).toBeCalled(); expect(gitHubClient.createOrUpdateFileAsync).toBeCalled();
@@ -221,7 +223,7 @@ describe("GitHubContentProvider save", () => {
it("errors on invalid path", async () => { it("errors on invalid path", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync"); spyOn(GitHubClient.prototype, "getContentsAsync");
const response = await gitHubContentProvider.save(null, "invalid path", null).toPromise(); const response = await gitHubContentProvider.save(undefined, "invalid path", undefined).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
expect(gitHubClient.getContentsAsync).not.toBeCalled(); expect(gitHubClient.getContentsAsync).not.toBeCalled();
@@ -230,7 +232,7 @@ describe("GitHubContentProvider save", () => {
it("errors on failed read", async () => { it("errors on failed read", async () => {
spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "getContentsAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.save(null, sampleGitHubUri, null).toPromise(); const response = await gitHubContentProvider.save(undefined, sampleGitHubUri, undefined).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -242,7 +244,7 @@ describe("GitHubContentProvider save", () => {
); );
spyOn(GitHubClient.prototype, "createOrUpdateFileAsync").and.returnValue(Promise.resolve({ status: 888 })); spyOn(GitHubClient.prototype, "createOrUpdateFileAsync").and.returnValue(Promise.resolve({ status: 888 }));
const response = await gitHubContentProvider.save(null, sampleGitHubUri, sampleNotebookModel).toPromise(); const response = await gitHubContentProvider.save(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(888); expect(response.status).toBe(888);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -257,7 +259,7 @@ describe("GitHubContentProvider save", () => {
Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit }) Promise.resolve({ status: HttpStatusCodes.OK, data: gitHubCommit })
); );
const response = await gitHubContentProvider.save(null, sampleGitHubUri, sampleNotebookModel).toPromise(); const response = await gitHubContentProvider.save(undefined, sampleGitHubUri, sampleNotebookModel).toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(HttpStatusCodes.OK); expect(response.status).toBe(HttpStatusCodes.OK);
expect(gitHubClient.getContentsAsync).toBeCalled(); expect(gitHubClient.getContentsAsync).toBeCalled();
@@ -271,7 +273,7 @@ describe("GitHubContentProvider save", () => {
describe("GitHubContentProvider listCheckpoints", () => { describe("GitHubContentProvider listCheckpoints", () => {
it("errors for everything", async () => { it("errors for everything", async () => {
const response = await gitHubContentProvider.listCheckpoints(null, null).toPromise(); const response = await gitHubContentProvider.listCheckpoints().toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
}); });
@@ -279,7 +281,7 @@ describe("GitHubContentProvider listCheckpoints", () => {
describe("GitHubContentProvider createCheckpoint", () => { describe("GitHubContentProvider createCheckpoint", () => {
it("errors for everything", async () => { it("errors for everything", async () => {
const response = await gitHubContentProvider.createCheckpoint(null, null).toPromise(); const response = await gitHubContentProvider.createCheckpoint().toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
}); });
@@ -287,7 +289,7 @@ describe("GitHubContentProvider createCheckpoint", () => {
describe("GitHubContentProvider deleteCheckpoint", () => { describe("GitHubContentProvider deleteCheckpoint", () => {
it("errors for everything", async () => { it("errors for everything", async () => {
const response = await gitHubContentProvider.deleteCheckpoint(null, null, null).toPromise(); const response = await gitHubContentProvider.deleteCheckpoint().toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
}); });
@@ -295,7 +297,7 @@ describe("GitHubContentProvider deleteCheckpoint", () => {
describe("GitHubContentProvider restoreFromCheckpoint", () => { describe("GitHubContentProvider restoreFromCheckpoint", () => {
it("errors for everything", async () => { it("errors for everything", async () => {
const response = await gitHubContentProvider.restoreFromCheckpoint(null, null, null).toPromise(); const response = await gitHubContentProvider.restoreFromCheckpoint().toPromise();
expect(response).toBeDefined(); expect(response).toBeDefined();
expect(response.status).toBe(GitHubContentProvider.SelfErrorCode); expect(response.status).toBe(GitHubContentProvider.SelfErrorCode);
}); });

View File

@@ -1,15 +1,15 @@
import { Notebook, stringifyNotebook, makeNotebookRecord, toJS } from "@nteract/commutable"; import { makeNotebookRecord, Notebook, stringifyNotebook, toJS } from "@nteract/commutable";
import { FileType, IContent, IContentProvider, IEmptyContent, IGetParams, ServerConfig } from "@nteract/core"; import { FileType, IContent, IContentProvider, IEmptyContent, IGetParams, ServerConfig } from "@nteract/core";
import { from, Observable, of } from "rxjs"; import { from, Observable, of } from "rxjs";
import { AjaxResponse } from "rxjs/ajax"; import { AjaxResponse } from "rxjs/ajax";
import * as Base64Utils from "../Utils/Base64Utils";
import { HttpStatusCodes } from "../Common/Constants"; 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"; 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 { GitHubClient, IGitHubFile, IGitHubResponse } from "./GitHubClient";
export interface GitHubContentProviderParams { export interface GitHubContentProviderParams {
gitHubClient: GitHubClient; gitHubClient: GitHubClient;
@@ -267,25 +267,25 @@ export class GitHubContentProvider implements IContentProvider {
); );
} }
public listCheckpoints(_: ServerConfig, path: string): Observable<AjaxResponse> { public listCheckpoints(): Observable<AjaxResponse> {
const error = new GitHubContentProviderError("Not implemented"); const error = new GitHubContentProviderError("Not implemented");
Logger.logError(error.message, "GitHubContentProvider/listCheckpoints", error.errno); Logger.logError(error.message, "GitHubContentProvider/listCheckpoints", error.errno);
return of(this.createErrorAjaxResponse(error)); return of(this.createErrorAjaxResponse(error));
} }
public createCheckpoint(_: ServerConfig, path: string): Observable<AjaxResponse> { public createCheckpoint(): Observable<AjaxResponse> {
const error = new GitHubContentProviderError("Not implemented"); const error = new GitHubContentProviderError("Not implemented");
Logger.logError(error.message, "GitHubContentProvider/createCheckpoint", error.errno); Logger.logError(error.message, "GitHubContentProvider/createCheckpoint", error.errno);
return of(this.createErrorAjaxResponse(error)); return of(this.createErrorAjaxResponse(error));
} }
public deleteCheckpoint(_: ServerConfig, path: string, checkpointID: string): Observable<AjaxResponse> { public deleteCheckpoint(): Observable<AjaxResponse> {
const error = new GitHubContentProviderError("Not implemented"); const error = new GitHubContentProviderError("Not implemented");
Logger.logError(error.message, "GitHubContentProvider/deleteCheckpoint", error.errno); Logger.logError(error.message, "GitHubContentProvider/deleteCheckpoint", error.errno);
return of(this.createErrorAjaxResponse(error)); return of(this.createErrorAjaxResponse(error));
} }
public restoreFromCheckpoint(_: ServerConfig, path: string, checkpointID: string): Observable<AjaxResponse> { public restoreFromCheckpoint(): Observable<AjaxResponse> {
const error = new GitHubContentProviderError("Not implemented"); const error = new GitHubContentProviderError("Not implemented");
Logger.logError(error.message, "GitHubContentProvider/restoreFromCheckpoint", error.errno); Logger.logError(error.message, "GitHubContentProvider/restoreFromCheckpoint", error.errno);
return of(this.createErrorAjaxResponse(error)); return of(this.createErrorAjaxResponse(error));

View File

@@ -26,7 +26,7 @@ export const SwitchAccount: FunctionComponent<Props> = ({
data: account, data: account,
}))} }))}
onChange={(_, option) => { onChange={(_, option) => {
setSelectedAccountName(String(option.key)); setSelectedAccountName(String(option?.key));
dismissMenu(); dismissMenu();
}} }}
defaultSelectedKey={selectedAccount?.name} defaultSelectedKey={selectedAccount?.name}

View File

@@ -26,7 +26,7 @@ export const SwitchSubscription: FunctionComponent<Props> = ({
}; };
})} })}
onChange={(_, option) => { onChange={(_, option) => {
setSelectedSubscriptionId(String(option.key)); setSelectedSubscriptionId(String(option?.key));
}} }}
defaultSelectedKey={selectedSubscription?.subscriptionId} defaultSelectedKey={selectedSubscription?.subscriptionId}
placeholder={subscriptions && subscriptions.length === 0 ? "No Subscriptions Found" : "Select a Subscription"} placeholder={subscriptions && subscriptions.length === 0 ? "No Subscriptions Found" : "Select a Subscription"}

View File

@@ -2,8 +2,8 @@ import * as DataModels from "../../../Contracts/DataModels";
import { parseConnectionString } from "./ConnectionStringParser"; import { parseConnectionString } from "./ConnectionStringParser";
describe("ConnectionStringParser", () => { describe("ConnectionStringParser", () => {
const mockAccountName: string = "Test"; const mockAccountName = "Test";
const mockMasterKey: string = "some-key"; const mockMasterKey = "some-key";
it("should parse a valid sql account connection string", () => { it("should parse a valid sql account connection string", () => {
const metadata = parseConnectionString( const metadata = parseConnectionString(

View File

@@ -40,7 +40,7 @@ export function getDatabaseAccountKindFromExperience(apiExperience: typeof userC
return AccountKind.GlobalDocumentDB; return AccountKind.GlobalDocumentDB;
} }
export function extractMasterKeyfromConnectionString(connectionString: string): string { export function extractMasterKeyfromConnectionString(connectionString: string): string | undefined {
// Only Gremlin uses the actual master key for connection to cosmos // Only Gremlin uses the actual master key for connection to cosmos
const matchedParts = connectionString.match("AccountKey=(.*);ApiKind=Gremlin;$"); const matchedParts = connectionString.match("AccountKey=(.*);ApiKind=Gremlin;$");
return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined; return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined;

View File

@@ -8,8 +8,8 @@ export type Features = {
readonly enableReactPane: boolean; readonly enableReactPane: boolean;
readonly enableRightPanelV2: boolean; readonly enableRightPanelV2: boolean;
readonly enableSchema: boolean; readonly enableSchema: boolean;
enableSchemaAnalyzer: boolean;
autoscaleDefault: boolean; autoscaleDefault: boolean;
partitionKeyDefault: boolean;
readonly enableSDKoperations: boolean; readonly enableSDKoperations: boolean;
readonly enableSpark: boolean; readonly enableSpark: boolean;
readonly enableTtl: boolean; readonly enableTtl: boolean;
@@ -53,7 +53,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
enableReactPane: "true" === get("enablereactpane"), enableReactPane: "true" === get("enablereactpane"),
enableRightPanelV2: "true" === get("enablerightpanelv2"), enableRightPanelV2: "true" === get("enablerightpanelv2"),
enableSchema: "true" === get("enableschema"), enableSchema: "true" === get("enableschema"),
enableSchemaAnalyzer: "true" === get("enableschemaanalyzer"),
enableSDKoperations: "true" === get("enablesdkoperations"), enableSDKoperations: "true" === get("enablesdkoperations"),
enableSpark: "true" === get("enablespark"), enableSpark: "true" === get("enablespark"),
enableTtl: "true" === get("enablettl"), enableTtl: "true" === get("enablettl"),
@@ -70,5 +69,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
showMinRUSurvey: "true" === get("showminrusurvey"), showMinRUSurvey: "true" === get("showminrusurvey"),
ttl90Days: "true" === get("ttl90days"), ttl90Days: "true" === get("ttl90days"),
autoscaleDefault: "true" === get("autoscaledefault"), autoscaleDefault: "true" === get("autoscaledefault"),
partitionKeyDefault: "true" === get("partitionkeytest"),
}; };
} }

View File

@@ -35,7 +35,7 @@ describe("Default Experience Utility", () => {
}); });
describe("getApiKindFromDefaultExperience()", () => { describe("getApiKindFromDefaultExperience()", () => {
function runScenario(defaultExperience: typeof userContext.apiType, expectedApiKind: number): void { function runScenario(defaultExperience: typeof userContext.apiType | null, expectedApiKind: number): void {
const resolvedApiKind = DefaultExperienceUtility.getApiKindFromDefaultExperience(defaultExperience); const resolvedApiKind = DefaultExperienceUtility.getApiKindFromDefaultExperience(defaultExperience);
expect(resolvedApiKind).toEqual(expectedApiKind); expect(resolvedApiKind).toEqual(expectedApiKind);
} }

View File

@@ -1,22 +1,17 @@
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { LocalStorageUtility, StorageKey } from "./StorageUtility"; import { LocalStorageUtility, StorageKey } from "./StorageUtility";
export class ExplorerSettings { export const createDefaultSettings = () => {
public static createDefaultSettings() { LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, Constants.Queries.itemsPerPage);
LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, Constants.Queries.itemsPerPage); LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, Constants.Queries.itemsPerPage);
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, Constants.Queries.itemsPerPage); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true");
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true"); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, Constants.Queries.DefaultMaxDegreeOfParallelism);
LocalStorageUtility.setEntryNumber( };
StorageKey.MaxDegreeOfParellism,
Constants.Queries.DefaultMaxDegreeOfParallelism
);
}
public static hasSettingsDefined(): boolean { export const hasSettingsDefined = (): boolean => {
return ( return (
LocalStorageUtility.hasItem(StorageKey.ActualItemPerPage) && LocalStorageUtility.hasItem(StorageKey.ActualItemPerPage) &&
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) && LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) &&
LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism) LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism)
); );
} };
}

View File

@@ -0,0 +1,22 @@
import { StorageKey } from "./StorageUtility";
import * as StringUtility from "./StringUtility";
export const hasItem = (key: StorageKey): boolean => !!localStorage.getItem(StorageKey[key]);
export const getEntryString = (key: StorageKey): string | null => localStorage.getItem(StorageKey[key]);
export const getEntryNumber = (key: StorageKey): number =>
StringUtility.toNumber(localStorage.getItem(StorageKey[key]));
export const getEntryBoolean = (key: StorageKey): boolean =>
StringUtility.toBoolean(localStorage.getItem(StorageKey[key]));
export const setEntryString = (key: StorageKey, value: string): void => localStorage.setItem(StorageKey[key], value);
export const removeEntry = (key: StorageKey): void => localStorage.removeItem(StorageKey[key]);
export const setEntryNumber = (key: StorageKey, value: number): void =>
localStorage.setItem(StorageKey[key], value.toString());
export const setEntryBoolean = (key: StorageKey, value: boolean): void =>
localStorage.setItem(StorageKey[key], value.toString());

View File

@@ -2,26 +2,26 @@ import * as Constants from "./Constants";
export function computeRUUsagePrice(serverId: string, requestUnits: number): string { export function computeRUUsagePrice(serverId: string, requestUnits: number): string {
if (serverId === "mooncake") { if (serverId === "mooncake") {
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU; const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU;
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency; return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency;
} }
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU; const ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency; return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
} }
export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string { export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string {
if (serverId === "mooncake") { if (serverId === "mooncake") {
let storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerGB; const storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerGB;
return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency; return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency;
} }
let storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerGB; const storageCharge = storageUsedRoundUpToGB * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerGB;
return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency; return calculateEstimateNumber(storageCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
} }
export function computeDisplayUsageString(usageInKB: number): string { export function computeDisplayUsageString(usageInKB: number): string {
let usageInMB = usageInKB / 1024, const usageInMB = usageInKB / 1024,
usageInGB = usageInMB / 1024, usageInGB = usageInMB / 1024,
displayUsageString = displayUsageString =
usageInGB > 0.1 usageInGB > 0.1
@@ -33,7 +33,7 @@ export function computeDisplayUsageString(usageInKB: number): string {
} }
export function usageInGB(usageInKB: number): number { export function usageInGB(usageInKB: number): number {
let usageInMB = usageInKB / 1024, const usageInMB = usageInKB / 1024,
usageInGB = usageInMB / 1024; usageInGB = usageInMB / 1024;
return Math.ceil(usageInGB); return Math.ceil(usageInGB);
} }

View File

@@ -0,0 +1,20 @@
import { StorageKey } from "./StorageUtility";
import * as StringUtility from "./StringUtility";
export const hasItem = (key: StorageKey): boolean => !!sessionStorage.getItem(StorageKey[key]);
export const getEntryString = (key: StorageKey): string | null => sessionStorage.getItem(StorageKey[key]);
export const getEntryNumber = (key: StorageKey): number =>
StringUtility.toNumber(sessionStorage.getItem(StorageKey[key]));
export const getEntry = (key: string): string | null => sessionStorage.getItem(key);
export const removeEntry = (key: StorageKey): void => sessionStorage.removeItem(StorageKey[key]);
export const setEntryString = (key: StorageKey, value: string): void => sessionStorage.setItem(StorageKey[key], value);
export const setEntry = (key: string, value: string): void => sessionStorage.setItem(key, value);
export const setEntryNumber = (key: StorageKey, value: number): void =>
sessionStorage.setItem(StorageKey[key], value.toString());

View File

@@ -1,73 +1,7 @@
import * as StringUtility from "./StringUtility"; import * as LocalStorageUtility from "./LocalStorageUtility";
import * as SessionStorageUtility from "./SessionStorageUtility";
export class LocalStorageUtility {
public static hasItem(key: StorageKey): boolean {
return !!localStorage.getItem(StorageKey[key]);
}
public static getEntryString(key: StorageKey): string | null {
return localStorage.getItem(StorageKey[key]);
}
public static getEntryNumber(key: StorageKey): number {
return StringUtility.toNumber(localStorage.getItem(StorageKey[key]));
}
public static getEntryBoolean(key: StorageKey): boolean {
return StringUtility.toBoolean(localStorage.getItem(StorageKey[key]));
}
public static setEntryString(key: StorageKey, value: string): void {
localStorage.setItem(StorageKey[key], value);
}
public static removeEntry(key: StorageKey): void {
return localStorage.removeItem(StorageKey[key]);
}
public static setEntryNumber(key: StorageKey, value: number): void {
localStorage.setItem(StorageKey[key], value.toString());
}
public static setEntryBoolean(key: StorageKey, value: boolean): void {
localStorage.setItem(StorageKey[key], value.toString());
}
}
export class SessionStorageUtility {
public static hasItem(key: StorageKey): boolean {
return !!sessionStorage.getItem(StorageKey[key]);
}
public static getEntryString(key: StorageKey): string | null {
return sessionStorage.getItem(StorageKey[key]);
}
public static getEntryNumber(key: StorageKey): number {
return StringUtility.toNumber(sessionStorage.getItem(StorageKey[key]));
}
public static getEntry(key: string): string | null {
return sessionStorage.getItem(key);
}
public static removeEntry(key: StorageKey): void {
return sessionStorage.removeItem(StorageKey[key]);
}
public static setEntryString(key: StorageKey, value: string): void {
sessionStorage.setItem(StorageKey[key], value);
}
public static setEntry(key: string, value: string): void {
sessionStorage.setItem(key, value);
}
public static setEntryNumber(key: StorageKey, value: number): void {
sessionStorage.setItem(StorageKey[key], value.toString());
}
}
export { LocalStorageUtility, SessionStorageUtility };
export enum StorageKey { export enum StorageKey {
ActualItemPerPage, ActualItemPerPage,
CustomItemPerPage, CustomItemPerPage,

View File

@@ -0,0 +1,13 @@
import { AuthType } from "../AuthType";
import * as DataModels from "../Contracts/DataModels";
import { ApiType } from "../UserContext";
export interface TerminalProps {
authToken: string;
notebookServerEndpoint: string;
terminalEndpoint: string;
databaseAccount: DataModels.DatabaseAccount;
authType: AuthType;
apiType: ApiType;
subscriptionId: string;
}

View File

@@ -1,43 +1,36 @@
import { ServerConnection } from "@jupyterlab/services"; import { ServerConnection } from "@jupyterlab/services";
import "@jupyterlab/terminal/style/index.css"; import "@jupyterlab/terminal/style/index.css";
import { HttpHeaders, TerminalQueryParams } from "../Common/Constants"; import postRobot from "post-robot";
import { HttpHeaders } from "../Common/Constants";
import { Action } from "../Shared/Telemetry/TelemetryConstants"; import { Action } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import "./index.css"; import "./index.css";
import { JupyterLabAppFactory } from "./JupyterLabAppFactory"; import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
import { TerminalProps } from "./TerminalProps";
const getUrlVars = (): { [key: string]: string } => { const createServerSettings = (props: TerminalProps): ServerConnection.ISettings => {
const vars: { [key: string]: string } = {};
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (_m, key, value): string => {
vars[key] = decodeURIComponent(value);
return value;
});
return vars;
};
const createServerSettings = (urlVars: { [key: string]: string }): ServerConnection.ISettings => {
let body: BodyInit | undefined; let body: BodyInit | undefined;
let headers: HeadersInit | undefined; let headers: HeadersInit | undefined;
if (urlVars.hasOwnProperty(TerminalQueryParams.TerminalEndpoint)) { if (props.terminalEndpoint) {
body = JSON.stringify({ body = JSON.stringify({
endpoint: urlVars[TerminalQueryParams.TerminalEndpoint], endpoint: props.terminalEndpoint,
}); });
headers = { headers = {
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json",
}; };
} }
const server = urlVars[TerminalQueryParams.Server]; const server = props.notebookServerEndpoint;
let options: Partial<ServerConnection.ISettings> = { let options: Partial<ServerConnection.ISettings> = {
baseUrl: server, baseUrl: server,
init: { body, headers }, init: { body, headers },
fetch: window.parent.fetch, fetch: window.parent.fetch,
}; };
if (urlVars.hasOwnProperty(TerminalQueryParams.Token)) { if (props.authToken) {
options = { options = {
baseUrl: server, baseUrl: server,
token: urlVars[TerminalQueryParams.Token], token: props.authToken,
appendToken: true, appendToken: true,
init: { body, headers }, init: { body, headers },
fetch: window.parent.fetch, fetch: window.parent.fetch,
@@ -47,30 +40,41 @@ const createServerSettings = (urlVars: { [key: string]: string }): ServerConnect
return ServerConnection.makeSettings(options); return ServerConnection.makeSettings(options);
}; };
const main = async (): Promise<void> => { const initTerminal = async (props: TerminalProps) => {
const urlVars = getUrlVars(); // Initialize userContext (only properties which are needed by TelemetryProcessor)
// Initialize userContext. Currently only subscriptionId is required by TelemetryProcessor
updateUserContext({ updateUserContext({
subscriptionId: urlVars[TerminalQueryParams.SubscriptionId], subscriptionId: props.subscriptionId,
apiType: props.apiType,
authType: props.authType,
databaseAccount: props.databaseAccount,
}); });
const serverSettings = createServerSettings(urlVars); const serverSettings = createServerSettings(props);
const data = { baseUrl: serverSettings.baseUrl }; const data = { baseUrl: serverSettings.baseUrl };
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data); const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
try { try {
if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) { await JupyterLabAppFactory.createTerminalApp(serverSettings);
await JupyterLabAppFactory.createTerminalApp(serverSettings);
} else {
throw new Error("Only terminal is supported");
}
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime); TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
} catch (error) { } catch (error) {
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime); TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);
} }
}; };
const main = async (): Promise<void> => {
postRobot.on(
"props",
{
window: window.parent,
domain: window.location.origin,
},
async (event) => {
// Typescript definition for event is wrong. So read props by casting to <any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const props = (event as any).data as TerminalProps;
await initTerminal(props);
}
);
};
window.addEventListener("load", main); window.addEventListener("load", main);

View File

@@ -1,13 +0,0 @@
import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext";
export class PortalTokenProvider implements ViewModels.TokenProvider {
constructor() {}
public async getAuthHeader(): Promise<Headers> {
const bearerToken = userContext.authorizationToken;
let fetchHeaders = new Headers();
fetchHeaders.append("authorization", bearerToken);
return fetchHeaders;
}
}

View File

@@ -1,20 +0,0 @@
import { configContext, Platform } from "../ConfigContext";
import * as ViewModels from "../Contracts/ViewModels";
import { PortalTokenProvider } from "./PortalTokenProvider";
export class TokenProviderFactory {
private constructor() {}
public static create(): ViewModels.TokenProvider {
const platformType = configContext.platform;
switch (platformType) {
case Platform.Portal:
case Platform.Hosted:
return new PortalTokenProvider();
case Platform.Emulator:
default:
// should never get into this state
throw new Error(`Unknown platform ${platformType}`);
}
}
}

View File

@@ -1,7 +1,12 @@
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
export const isCapabilityEnabled = (capabilityName: string): boolean => export const isCapabilityEnabled = (capabilityName: string): boolean => {
userContext.databaseAccount?.properties?.capabilities?.some((capability) => capability.name === capabilityName); const { databaseAccount } = userContext;
if (databaseAccount && databaseAccount.properties && databaseAccount.properties.capabilities) {
return databaseAccount.properties.capabilities.some((capability) => capability.name === capabilityName);
}
return false;
};
export const isServerlessAccount = (): boolean => isCapabilityEnabled(Constants.CapabilityNames.EnableServerless); export const isServerlessAccount = (): boolean => isCapabilityEnabled(Constants.CapabilityNames.EnableServerless);

View File

@@ -18,7 +18,7 @@ describe("PricingUtils Tests", () => {
}); });
it("should return false if passed number is not number", () => { it("should return false if passed number is not number", () => {
const value = PricingUtils.isLargerThanDefaultMinRU(null); const value = PricingUtils.isLargerThanDefaultMinRU(undefined);
expect(value).toBe(false); expect(value).toBe(false);
}); });
}); });
@@ -28,7 +28,7 @@ describe("PricingUtils Tests", () => {
const value = PricingUtils.computeRUUsagePriceHourly({ const value = PricingUtils.computeRUUsagePriceHourly({
serverId: "default", serverId: "default",
requestUnits: 1, requestUnits: 1,
numberOfRegions: null, numberOfRegions: undefined,
multimasterEnabled: false, multimasterEnabled: false,
isAutoscale: false, isAutoscale: false,
}); });
@@ -38,7 +38,7 @@ describe("PricingUtils Tests", () => {
const value = PricingUtils.computeRUUsagePriceHourly({ const value = PricingUtils.computeRUUsagePriceHourly({
serverId: "default", serverId: "default",
requestUnits: 1, requestUnits: 1,
numberOfRegions: null, numberOfRegions: undefined,
multimasterEnabled: false, multimasterEnabled: false,
isAutoscale: true, isAutoscale: true,
}); });
@@ -264,11 +264,6 @@ describe("PricingUtils Tests", () => {
describe("getRegionMultiplier()", () => { describe("getRegionMultiplier()", () => {
describe("without multimaster", () => { describe("without multimaster", () => {
it("should return 0 for null", () => {
const value = PricingUtils.getRegionMultiplier(null, false);
expect(value).toBe(0);
});
it("should return 0 for undefined", () => { it("should return 0 for undefined", () => {
const value = PricingUtils.getRegionMultiplier(undefined, false); const value = PricingUtils.getRegionMultiplier(undefined, false);
expect(value).toBe(0); expect(value).toBe(0);
@@ -296,11 +291,6 @@ describe("PricingUtils Tests", () => {
}); });
describe("with multimaster", () => { describe("with multimaster", () => {
it("should return 0 for null", () => {
const value = PricingUtils.getRegionMultiplier(null, true);
expect(value).toBe(0);
});
it("should return 0 for undefined", () => { it("should return 0 for undefined", () => {
const value = PricingUtils.getRegionMultiplier(undefined, true); const value = PricingUtils.getRegionMultiplier(undefined, true);
expect(value).toBe(0); expect(value).toBe(0);
@@ -450,11 +440,6 @@ describe("PricingUtils Tests", () => {
}); });
describe("normalizeNumberOfRegions()", () => { describe("normalizeNumberOfRegions()", () => {
it("should return 0 for null", () => {
const value = PricingUtils.normalizeNumber(null);
expect(value).toBe(0);
});
it("should return 0 for undefined", () => { it("should return 0 for undefined", () => {
const value = PricingUtils.normalizeNumber(undefined); const value = PricingUtils.normalizeNumber(undefined);
expect(value).toBe(0); expect(value).toBe(0);

View File

@@ -5,23 +5,19 @@ import * as ViewModels from "../Contracts/ViewModels";
import * as QueryUtils from "./QueryUtils"; import * as QueryUtils from "./QueryUtils";
describe("Query Utils", () => { describe("Query Utils", () => {
function generatePartitionKeyForPath(path: string): DataModels.PartitionKey { const generatePartitionKeyForPath = (path: string): DataModels.PartitionKey => {
return { return {
paths: [path], paths: [path],
kind: "Hash", kind: "Hash",
version: 2, version: 2,
}; };
} };
describe("buildDocumentsQueryPartitionProjections()", () => { describe("buildDocumentsQueryPartitionProjections()", () => {
it("should return empty string if partition key is undefined", () => { it("should return empty string if partition key is undefined", () => {
expect(QueryUtils.buildDocumentsQueryPartitionProjections("c", undefined)).toBe(""); expect(QueryUtils.buildDocumentsQueryPartitionProjections("c", undefined)).toBe("");
}); });
it("should return empty string if partition key is null", () => {
expect(QueryUtils.buildDocumentsQueryPartitionProjections("c", null)).toBe("");
});
it("should replace slashes and embed projection in square braces", () => { it("should replace slashes and embed projection in square braces", () => {
const partitionKey: DataModels.PartitionKey = generatePartitionKeyForPath("/a"); const partitionKey: DataModels.PartitionKey = generatePartitionKeyForPath("/a");
const partitionProjection: string = QueryUtils.buildDocumentsQueryPartitionProjections("c", partitionKey); const partitionProjection: string = QueryUtils.buildDocumentsQueryPartitionProjections("c", partitionKey);

View File

@@ -3,27 +3,27 @@ import * as StringUtils from "./StringUtils";
describe("StringUtils", () => { describe("StringUtils", () => {
describe("stripSpacesFromString()", () => { describe("stripSpacesFromString()", () => {
it("should strip all spaces from input string", () => { it("should strip all spaces from input string", () => {
const transformedString: string = StringUtils.stripSpacesFromString("a b c"); const transformedString: string | undefined = StringUtils.stripSpacesFromString("a b c");
expect(transformedString).toBe("abc"); expect(transformedString).toBe("abc");
}); });
it("should return original string if input string has no spaces", () => { it("should return original string if input string has no spaces", () => {
const transformedString: string = StringUtils.stripSpacesFromString("abc"); const transformedString: string | undefined = StringUtils.stripSpacesFromString("abc");
expect(transformedString).toBe("abc"); expect(transformedString).toBe("abc");
}); });
it("should return undefined if input is undefined", () => { it("should return undefined if input is undefined", () => {
const transformedString: string = StringUtils.stripSpacesFromString(undefined); const transformedString: string | undefined = StringUtils.stripSpacesFromString(undefined);
expect(transformedString).toBeUndefined(); expect(transformedString).toBeUndefined();
}); });
it("should return undefined if input is undefiend", () => { it("should return undefined if input is undefiend", () => {
const transformedString: string = StringUtils.stripSpacesFromString(undefined); const transformedString: string | undefined = StringUtils.stripSpacesFromString(undefined);
expect(transformedString).toBe(undefined); expect(transformedString).toBe(undefined);
}); });
it("should return empty string if input is an empty string", () => { it("should return empty string if input is an empty string", () => {
const transformedString: string = StringUtils.stripSpacesFromString(""); const transformedString: string | undefined = StringUtils.stripSpacesFromString("");
expect(transformedString).toBe(""); expect(transformedString).toBe("");
}); });
}); });

View File

@@ -1,4 +1,4 @@
export function stripSpacesFromString(inputString: string): string { export function stripSpacesFromString(inputString?: string): string | undefined {
if (inputString === undefined || typeof inputString !== "string") { if (inputString === undefined || typeof inputString !== "string") {
return inputString; return inputString;
} }

View File

@@ -15,7 +15,7 @@ export async function fetchDatabaseAccounts(subscriptionId: string, accessToken:
let accounts: Array<DatabaseAccount> = []; let accounts: Array<DatabaseAccount> = [];
let nextLink = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=2020-06-01-preview`; let nextLink = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=2021-06-15`;
while (nextLink) { while (nextLink) {
const response: Response = await fetch(nextLink, { headers }); const response: Response = await fetch(nextLink, { headers });

View File

@@ -105,7 +105,9 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
aadToken = aadTokenResponse.accessToken; aadToken = aadTokenResponse.accessToken;
} }
try { try {
keys = await listKeys(subscriptionId, resourceGroup, account.name); if (!account.properties.disableLocalAuth) {
keys = await listKeys(subscriptionId, resourceGroup, account.name);
}
} catch (e) { } catch (e) {
if (userContext.features.enableAadDataPlane) { if (userContext.features.enableAadDataPlane) {
console.warn(e); console.warn(e);
@@ -326,8 +328,8 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (inputs.flights.indexOf(Flights.AutoscaleTest) !== -1) { if (inputs.flights.indexOf(Flights.AutoscaleTest) !== -1) {
userContext.features.autoscaleDefault; userContext.features.autoscaleDefault;
} }
if (inputs.flights.indexOf(Flights.SchemaAnalyzer) !== -1) { if (inputs.flights.indexOf(Flights.PartitionKeyTest) !== -1) {
userContext.features.enableSchemaAnalyzer = true; userContext.features.partitionKeyDefault = true;
} }
} }
} }

View File

@@ -1,8 +1,8 @@
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
export interface NotebookSnapshotHooks { export interface NotebookSnapshotHooks {
snapshot: string; snapshot?: string;
error: string; error?: string;
setSnapshot: (imageSrc: string) => void; setSnapshot: (imageSrc: string) => void;
setError: (error: string) => void; setError: (error: string) => void;
} }

View File

@@ -24,8 +24,8 @@ export async function fetchAccessData(portalToken: string): Promise<AccessInputM
); );
} }
export function useTokenMetadata(token: string): AccessInputMetadata { export function useTokenMetadata(token: string): AccessInputMetadata | undefined {
const [state, setState] = useState<AccessInputMetadata>(); const [state, setState] = useState<AccessInputMetadata | undefined>();
useEffect(() => { useEffect(() => {
if (token) { if (token) {

View File

@@ -58,6 +58,7 @@
"./src/Explorer/Notebook/NotebookRenderer/AzureTheme.tsx", "./src/Explorer/Notebook/NotebookRenderer/AzureTheme.tsx",
"./src/Explorer/Notebook/NotebookRenderer/Prompt.tsx", "./src/Explorer/Notebook/NotebookRenderer/Prompt.tsx",
"./src/Explorer/Notebook/NotebookRenderer/PromptContent.tsx", "./src/Explorer/Notebook/NotebookRenderer/PromptContent.tsx",
"./src/Explorer/Notebook/NotebookRenderer/StatusBar.tsx",
"./src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx", "./src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx",
"./src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx", "./src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx",
"./src/Explorer/Notebook/NotebookUtil.ts", "./src/Explorer/Notebook/NotebookUtil.ts",
@@ -65,12 +66,16 @@
"./src/Explorer/Notebook/SchemaAnalyzer/SchemaAnalyzerUtils.ts", "./src/Explorer/Notebook/SchemaAnalyzer/SchemaAnalyzerUtils.ts",
"./src/Explorer/OpenFullScreen.test.tsx", "./src/Explorer/OpenFullScreen.test.tsx",
"./src/Explorer/OpenFullScreen.tsx", "./src/Explorer/OpenFullScreen.tsx",
"./src/Explorer/Panes/PanelContainerComponent.test.tsx",
"./src/Explorer/Panes/PanelContainerComponent.tsx",
"./src/Explorer/Panes/PanelFooterComponent.tsx", "./src/Explorer/Panes/PanelFooterComponent.tsx",
"./src/Explorer/Panes/PanelInfoErrorComponent.tsx", "./src/Explorer/Panes/PanelInfoErrorComponent.tsx",
"./src/Explorer/Panes/PanelLoadingScreen.tsx", "./src/Explorer/Panes/PanelLoadingScreen.tsx",
"./src/Explorer/Panes/PanelStyles.ts",
"./src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts", "./src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts",
"./src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts", "./src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts",
"./src/Explorer/Tables/Constants.ts", "./src/Explorer/Tables/Constants.ts",
"./src/Explorer/Tables/CqlUtilities.test.ts",
"./src/Explorer/Tables/CqlUtilities.ts", "./src/Explorer/Tables/CqlUtilities.ts",
"./src/Explorer/Tables/DataTable/CacheBase.ts", "./src/Explorer/Tables/DataTable/CacheBase.ts",
"./src/Explorer/Tables/Entities.ts", "./src/Explorer/Tables/Entities.ts",
@@ -84,6 +89,8 @@
"./src/Platform/Hosted/Components/MeControl.test.tsx", "./src/Platform/Hosted/Components/MeControl.test.tsx",
"./src/Platform/Hosted/Components/MeControl.tsx", "./src/Platform/Hosted/Components/MeControl.tsx",
"./src/Platform/Hosted/Components/SignInButton.tsx", "./src/Platform/Hosted/Components/SignInButton.tsx",
"./src/Platform/Hosted/HostedUtils.test.ts",
"./src/Platform/Hosted/HostedUtils.ts",
"./src/Platform/Hosted/extractFeatures.test.ts", "./src/Platform/Hosted/extractFeatures.test.ts",
"./src/Platform/Hosted/extractFeatures.ts", "./src/Platform/Hosted/extractFeatures.ts",
"./src/ReactDevTools.ts", "./src/ReactDevTools.ts",
@@ -93,7 +100,9 @@
"./src/Shared/Constants.ts", "./src/Shared/Constants.ts",
"./src/Shared/DefaultExperienceUtility.ts", "./src/Shared/DefaultExperienceUtility.ts",
"./src/Shared/ExplorerSettings.ts", "./src/Shared/ExplorerSettings.ts",
"./src/Shared/LocalStorageUtility.ts",
"./src/Shared/PriceEstimateCalculator.ts", "./src/Shared/PriceEstimateCalculator.ts",
"./src/Shared/SessionStorageUtility.ts",
"./src/Shared/StorageUtility.test.ts", "./src/Shared/StorageUtility.test.ts",
"./src/Shared/StorageUtility.ts", "./src/Shared/StorageUtility.ts",
"./src/Shared/StringUtility.test.ts", "./src/Shared/StringUtility.test.ts",
@@ -105,12 +114,15 @@
"./src/Utils/Base64Utils.test.ts", "./src/Utils/Base64Utils.test.ts",
"./src/Utils/Base64Utils.ts", "./src/Utils/Base64Utils.ts",
"./src/Utils/BlobUtils.ts", "./src/Utils/BlobUtils.ts",
"./src/Utils/CapabilityUtils.ts",
"./src/Utils/CloudUtils.ts",
"./src/Utils/GitHubUtils.test.ts", "./src/Utils/GitHubUtils.test.ts",
"./src/Utils/GitHubUtils.ts", "./src/Utils/GitHubUtils.ts",
"./src/Utils/MessageValidation.test.ts", "./src/Utils/MessageValidation.test.ts",
"./src/Utils/MessageValidation.ts", "./src/Utils/MessageValidation.ts",
"./src/Utils/NotificationConsoleUtils.ts", "./src/Utils/NotificationConsoleUtils.ts",
"./src/Utils/PricingUtils.ts", "./src/Utils/PricingUtils.ts",
"./src/Utils/StringUtils.test.ts",
"./src/Utils/StringUtils.ts", "./src/Utils/StringUtils.ts",
"./src/Utils/StyleUtils.ts", "./src/Utils/StyleUtils.ts",
"./src/Utils/WindowUtils.test.ts", "./src/Utils/WindowUtils.test.ts",
@@ -119,6 +131,8 @@
"./src/hooks/useDirectories.tsx", "./src/hooks/useDirectories.tsx",
"./src/hooks/useFullScreenURLs.tsx", "./src/hooks/useFullScreenURLs.tsx",
"./src/hooks/useGraphPhoto.tsx", "./src/hooks/useGraphPhoto.tsx",
"./src/hooks/useNotebookSnapshotStore.ts",
"./src/hooks/usePortalAccessToken.tsx",
"./src/hooks/useNotificationConsole.ts", "./src/hooks/useNotificationConsole.ts",
"./src/hooks/useObservable.ts", "./src/hooks/useObservable.ts",
"./src/hooks/useSidePanel.ts", "./src/hooks/useSidePanel.ts",
@@ -126,7 +140,9 @@
"./src/quickstart.ts", "./src/quickstart.ts",
"./src/setupTests.ts", "./src/setupTests.ts",
"./src/userContext.test.ts", "./src/userContext.test.ts",
"src/Common/EntityValue.tsx" "src/Common/EntityValue.tsx",
"./src/Platform/Hosted/Components/SwitchAccount.tsx",
"./src/Platform/Hosted/Components/SwitchSubscription.tsx",
], ],
"include": [ "include": [
"src/CellOutputViewer/transforms/**/*", "src/CellOutputViewer/transforms/**/*",
@@ -152,4 +168,4 @@
"src/Terminal/**/*", "src/Terminal/**/*",
"src/Utils/arm/**/*" "src/Utils/arm/**/*"
] ]
} }