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/DataSamplesUtil.test.ts
src/Explorer/DataSamples/DataSamplesUtil.ts
src/Explorer/Explorer.tsx
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts
src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts
src/Explorer/Graph/GraphExplorerComponent/D3ForceGraph.test.ts
@@ -83,11 +82,6 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
# src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts
# src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
src/Explorer/Menus/ContextMenu.ts
src/Explorer/MostRecentActivity/MostRecentActivity.ts
src/Explorer/Notebook/NotebookClientV2.ts
@@ -141,7 +135,6 @@ src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts
src/Explorer/Tabs/UserDefinedFunctionTab.ts
src/Explorer/Tree/AccessibleVerticalList.ts
src/Explorer/Tree/Collection.test.ts
src/Explorer/Tree/Collection.ts
src/Explorer/Tree/ConflictId.ts
src/Explorer/Tree/DocumentId.ts
@@ -150,65 +143,32 @@ src/Explorer/Tree/ResourceTokenCollection.ts
src/Explorer/Tree/StoredProcedure.ts
src/Explorer/Tree/TreeComponents.ts
src/Explorer/Tree/Trigger.ts
src/Explorer/Tree/UserDefinedFunction.ts
src/Explorer/WaitsForTemplateViewModel.ts
src/GitHub/GitHubClient.test.ts
src/GitHub/GitHubClient.ts
src/GitHub/GitHubConnector.ts
src/GitHub/GitHubContentProvider.test.ts
src/GitHub/GitHubContentProvider.ts
src/GitHub/GitHubOAuthService.ts
src/Index.ts
src/Juno/JunoClient.test.ts
src/Juno/JunoClient.ts
src/Platform/Hosted/Authorization.ts
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
src/ReactDevTools.ts
src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts
src/Shared/ExplorerSettings.ts
src/Shared/PriceEstimateCalculator.ts
src/Shared/StorageUtility.test.ts
src/Shared/StorageUtility.ts
src/Shared/appInsights.ts
src/SparkClusterManager/ArcadiaResourceManager.ts
src/SparkClusterManager/SparkClusterManager.ts
src/Terminal/JupyterLabAppFactory.ts
src/Terminal/NotebookAppContracts.d.ts
src/Terminal/index.ts
src/TokenProviders/PortalTokenProvider.ts
src/TokenProviders/TokenProviderFactory.ts
src/Utils/PricingUtils.test.ts
src/Utils/QueryUtils.test.ts
src/applyExplorerBindings.ts
src/global.d.ts
src/setupTests.ts
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
src/Explorer/Controls/Accordion/AccordionComponent.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx
src/Explorer/Controls/AccountSwitch/AccountSwitchComponentAdapter.tsx
src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx
src/Explorer/Controls/CollapsiblePanel/CollapsiblePanel.tsx
src/Explorer/Controls/CommandButton/CommandButtonComponent.tsx
src/Explorer/Controls/DialogReactComponent/DialogComponent.tsx
src/Explorer/Controls/DialogReactComponent/DialogComponentAdapter.tsx
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.test.tsx
src/Explorer/Controls/Directory/DefaultDirectoryDropdownComponent.tsx
src/Explorer/Controls/Directory/DirectoryComponentAdapter.tsx
src/Explorer/Controls/Directory/DirectoryListComponent.test.tsx
src/Explorer/Controls/Directory/DirectoryListComponent.tsx
src/Explorer/Controls/Editor/EditorReact.tsx
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx
src/NotebookViewer/NotebookViewer.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/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.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/NodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/QueryContainerComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNeighborsComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
src/Explorer/Notebook/NotebookComponent/contents/file/index.tsx
src/Explorer/Notebook/NotebookComponent/contents/file/text-file.tsx
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
src/Explorer/Notebook/NotebookRenderer/AzureTheme.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/Prompt.tsx
src/Explorer/Notebook/NotebookRenderer/PromptContent.tsx
src/Explorer/Notebook/NotebookRenderer/StatusBar.test.tsx
src/Explorer/Notebook/NotebookRenderer/StatusBar.tsx
src/Explorer/Notebook/NotebookRenderer/Toolbar.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/HoverableCell.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx
src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx
src/Explorer/Notebook/temp/inputs/editor.tsx
src/Explorer/Notebook/temp/markdown-cell.tsx
src/Explorer/Notebook/temp/source.tsx
src/Explorer/Notebook/temp/syntax-highlighter/index.tsx
src/Explorer/SplashScreen/SplashScreen.tsx
src/Explorer/Tabs/GalleryTab.tsx
src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx
src/Explorer/Tree/ResourceTreeAdapter.tsx
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
__mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,9 @@ import TriangleDownIcon from "../../../../images/Triangle-down.svg";
import TriangleRightIcon from "../../../../images/Triangle-right.svg";
import * as Constants from "../../../Common/Constants";
export interface AccordionComponentProps {}
export interface AccordionComponentProps {
children: React.ReactNode;
}
export class AccordionComponent extends React.Component<AccordionComponentProps> {
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 });
};

View File

@@ -121,8 +121,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
if (!this.dropdownElt || !this.expandButtonElt) {
return;
}
const dropdownElt = $(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
$(this.dropdownElt).offset({ left: $(this.expandButtonElt).offset().left });
}
private onKeyPress(event: React.KeyboardEvent): boolean {

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import * as React from "react";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import CloseIcon from "../../../../images/close-black.svg";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
export interface QueryContainerComponentProps {
initialQuery: string;
@@ -82,7 +82,7 @@ export class QueryContainerComponent extends React.Component<
<button
type="button"
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)}
>
Execute Gremlin Query

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@ import { userContext } from "../../UserContext";
import { getFullName } from "../../Utils/UserUtils";
import Explorer from "../Explorer";
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { InMemoryContentProvider } from "./NotebookComponent/ContentProviders/InMemoryContentProvider";
@@ -88,7 +89,18 @@ export default class NotebookManager {
this.gitHubClient.setToken(token?.access_token);
if (this?.gitHubOAuthService.isLoggedIn()) {
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();
@@ -161,7 +173,17 @@ export default class NotebookManager {
undefined,
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
"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",
undefined
);

View File

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

View File

@@ -1,6 +1,5 @@
import { shallow } from "enzyme";
import React from "react";
import { StatusBar } from "./StatusBar";
describe("StatusBar", () => {
@@ -28,8 +27,8 @@ describe("StatusBar", () => {
kernelSpecDisplayName: "javascript",
kernelStatus: "kernelStatus",
},
null,
null
undefined,
undefined
);
expect(shouldUpdate).toBe(true);
});
@@ -47,8 +46,8 @@ describe("StatusBar", () => {
kernelSpecDisplayName: "python3",
kernelStatus: "kernelStatus",
},
null,
null
undefined,
undefined
);
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 React from "react";
import { connect } from "react-redux";
import styled from "styled-components";
import { StyleConstants } from "../../../Common/Constants";
interface Props {
@@ -12,8 +13,6 @@ interface Props {
const NOT_CONNECTED = "not connected";
import styled from "styled-components";
export const LeftStatus = styled.div`
float: left;
display: block;
@@ -80,7 +79,7 @@ interface InitialProps {
contentRef: ContentRef;
}
const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => {
const makeMapStateToProps = (_initialState: AppState, initialProps: InitialProps): ((state: AppState) => Props) => {
const { contentRef } = initialProps;
const mapStateToProps = (state: AppState) => {
@@ -90,26 +89,26 @@ const makeMapStateToProps = (initialState: AppState, initialProps: InitialProps)
return {
kernelStatus: NOT_CONNECTED,
kernelSpecDisplayName: "no kernel",
lastSaved: null,
lastSaved: undefined,
};
}
const kernelRef = content.model.kernelRef;
let kernel = null;
let kernel;
if (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
// so we can pluck off the display_name and provide it here
let kernelSpecDisplayName = " ";
if (kernelStatus === NOT_CONNECTED) {
kernelSpecDisplayName = "no kernel";
} else if (kernel != null && kernel.kernelSpecName != null) {
} else if (kernel?.kernelSpecName) {
kernelSpecDisplayName = kernel.kernelSpecName;
} else if (content && content.type === "notebook") {
kernelSpecDisplayName = selectors.notebook.displayName(content.model) || " ";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,18 +4,12 @@ import Collection from "./Collection";
jest.mock("monaco-editor");
describe("Collection", () => {
function generateCollection(
container: Explorer,
databaseId: string,
data: DataModels.Collection,
offer: DataModels.Offer
): Collection {
return new Collection(container, databaseId, data);
}
const generateCollection = (container: Explorer, databaseId: string, data: DataModels.Collection): Collection =>
new Collection(container, databaseId, data);
function generateMockCollectionsDataModelWithPartitionKey(
const generateMockCollectionsDataModelWithPartitionKey = (
partitionKey: DataModels.PartitionKey
): DataModels.Collection {
): DataModels.Collection => {
return {
defaultTtl: 1,
indexingPolicy: {} as DataModels.IndexingPolicy,
@@ -26,13 +20,12 @@ describe("Collection", () => {
_ts: 1,
id: "",
};
}
};
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
const generateMockCollectionWithDataModel = (data: DataModels.Collection): Collection => {
const mockContainer = {} as Explorer;
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
}
return generateCollection(mockContainer, "abc", data);
};
describe("Partition key path parsing", () => {
let collection: Collection;
@@ -88,7 +81,7 @@ describe("Collection", () => {
kind: "Hash",
});
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);
}
public onNewUserDefinedFunctionClick(source: ViewModels.Collection, event: MouseEvent) {
UserDefinedFunction.create(source, event);
public onNewUserDefinedFunctionClick(source: ViewModels.Collection) {
UserDefinedFunction.create(source);
}
public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) {

View File

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

View File

@@ -30,7 +30,7 @@ export default class UserDefinedFunction {
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 userDefinedFunction = {
id: "",
@@ -104,7 +104,9 @@ export default class UserDefinedFunction {
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
() => {
/**/
}
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ export function getDatabaseAccountKindFromExperience(apiExperience: typeof userC
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
const matchedParts = connectionString.match("AccountKey=(.*);ApiKind=Gremlin;$");
return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined;

View File

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

View File

@@ -35,7 +35,7 @@ describe("Default Experience Utility", () => {
});
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);
expect(resolvedApiKind).toEqual(expectedApiKind);
}

View File

@@ -1,22 +1,17 @@
import * as Constants from "../Common/Constants";
import { LocalStorageUtility, StorageKey } from "./StorageUtility";
export class ExplorerSettings {
public static createDefaultSettings() {
LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, Constants.Queries.itemsPerPage);
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, Constants.Queries.itemsPerPage);
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true");
LocalStorageUtility.setEntryNumber(
StorageKey.MaxDegreeOfParellism,
Constants.Queries.DefaultMaxDegreeOfParallelism
);
}
export const createDefaultSettings = () => {
LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, Constants.Queries.itemsPerPage);
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, Constants.Queries.itemsPerPage);
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true");
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, Constants.Queries.DefaultMaxDegreeOfParallelism);
};
public static hasSettingsDefined(): boolean {
return (
LocalStorageUtility.hasItem(StorageKey.ActualItemPerPage) &&
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) &&
LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism)
);
}
}
export const hasSettingsDefined = (): boolean => {
return (
LocalStorageUtility.hasItem(StorageKey.ActualItemPerPage) &&
LocalStorageUtility.hasItem(StorageKey.IsCrossPartitionQueryEnabled) &&
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 {
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;
}
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;
}
export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string {
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;
}
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;
}
export function computeDisplayUsageString(usageInKB: number): string {
let usageInMB = usageInKB / 1024,
const usageInMB = usageInKB / 1024,
usageInGB = usageInMB / 1024,
displayUsageString =
usageInGB > 0.1
@@ -33,7 +33,7 @@ export function computeDisplayUsageString(usageInKB: number): string {
}
export function usageInGB(usageInKB: number): number {
let usageInMB = usageInKB / 1024,
const usageInMB = usageInKB / 1024,
usageInGB = usageInMB / 1024;
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";
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());
}
}
import * as LocalStorageUtility from "./LocalStorageUtility";
import * as SessionStorageUtility from "./SessionStorageUtility";
export { LocalStorageUtility, SessionStorageUtility };
export enum StorageKey {
ActualItemPerPage,
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 "@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 * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { updateUserContext } from "../UserContext";
import "./index.css";
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
import { TerminalProps } from "./TerminalProps";
const getUrlVars = (): { [key: string]: string } => {
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 => {
const createServerSettings = (props: TerminalProps): ServerConnection.ISettings => {
let body: BodyInit | undefined;
let headers: HeadersInit | undefined;
if (urlVars.hasOwnProperty(TerminalQueryParams.TerminalEndpoint)) {
if (props.terminalEndpoint) {
body = JSON.stringify({
endpoint: urlVars[TerminalQueryParams.TerminalEndpoint],
endpoint: props.terminalEndpoint,
});
headers = {
[HttpHeaders.contentType]: "application/json",
};
}
const server = urlVars[TerminalQueryParams.Server];
const server = props.notebookServerEndpoint;
let options: Partial<ServerConnection.ISettings> = {
baseUrl: server,
init: { body, headers },
fetch: window.parent.fetch,
};
if (urlVars.hasOwnProperty(TerminalQueryParams.Token)) {
if (props.authToken) {
options = {
baseUrl: server,
token: urlVars[TerminalQueryParams.Token],
token: props.authToken,
appendToken: true,
init: { body, headers },
fetch: window.parent.fetch,
@@ -47,30 +40,41 @@ const createServerSettings = (urlVars: { [key: string]: string }): ServerConnect
return ServerConnection.makeSettings(options);
};
const main = async (): Promise<void> => {
const urlVars = getUrlVars();
// Initialize userContext. Currently only subscriptionId is required by TelemetryProcessor
const initTerminal = async (props: TerminalProps) => {
// Initialize userContext (only properties which are needed by TelemetryProcessor)
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 startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
try {
if (urlVars.hasOwnProperty(TerminalQueryParams.Terminal)) {
await JupyterLabAppFactory.createTerminalApp(serverSettings);
} else {
throw new Error("Only terminal is supported");
}
await JupyterLabAppFactory.createTerminalApp(serverSettings);
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
} catch (error) {
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);

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 { userContext } from "../UserContext";
export const isCapabilityEnabled = (capabilityName: string): boolean =>
userContext.databaseAccount?.properties?.capabilities?.some((capability) => capability.name === capabilityName);
export const isCapabilityEnabled = (capabilityName: string): boolean => {
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);

View File

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

View File

@@ -5,23 +5,19 @@ import * as ViewModels from "../Contracts/ViewModels";
import * as QueryUtils from "./QueryUtils";
describe("Query Utils", () => {
function generatePartitionKeyForPath(path: string): DataModels.PartitionKey {
const generatePartitionKeyForPath = (path: string): DataModels.PartitionKey => {
return {
paths: [path],
kind: "Hash",
version: 2,
};
}
};
describe("buildDocumentsQueryPartitionProjections()", () => {
it("should return empty string if partition key is undefined", () => {
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", () => {
const partitionKey: DataModels.PartitionKey = generatePartitionKeyForPath("/a");
const partitionProjection: string = QueryUtils.buildDocumentsQueryPartitionProjections("c", partitionKey);

View File

@@ -3,27 +3,27 @@ import * as StringUtils from "./StringUtils";
describe("StringUtils", () => {
describe("stripSpacesFromString()", () => {
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");
});
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");
});
it("should return undefined if input is undefined", () => {
const transformedString: string = StringUtils.stripSpacesFromString(undefined);
const transformedString: string | undefined = StringUtils.stripSpacesFromString(undefined);
expect(transformedString).toBeUndefined();
});
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);
});
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("");
});
});

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") {
return inputString;
}

View File

@@ -15,7 +15,7 @@ export async function fetchDatabaseAccounts(subscriptionId: string, accessToken:
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) {
const response: Response = await fetch(nextLink, { headers });

View File

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

View File

@@ -1,8 +1,8 @@
import create, { UseStore } from "zustand";
export interface NotebookSnapshotHooks {
snapshot: string;
error: string;
snapshot?: string;
error?: string;
setSnapshot: (imageSrc: 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 {
const [state, setState] = useState<AccessInputMetadata>();
export function useTokenMetadata(token: string): AccessInputMetadata | undefined {
const [state, setState] = useState<AccessInputMetadata | undefined>();
useEffect(() => {
if (token) {

View File

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