Compare commits

...

11 Commits

Author SHA1 Message Date
vaidankarswapnil
f296c00a1c Fixed graph style panel close issue (#858) 2021-06-07 10:38:13 -05:00
victor-meng
7d0be7d355 Show 10k RU instead of unlimited as max RU for unsharded collection (#845) 2021-05-28 20:11:31 -05:00
Steve Faulkner
04b3ef051a Revert "Upgrade Monaco Editor (#847)" (#850)
This reverts commit 5e2b8d7df0.
2021-05-28 15:49:29 -05:00
Steve Faulkner
b875407d49 Pure React Command Bar (#828) 2021-05-28 15:20:59 -05:00
Steve Faulkner
18ce8749ed Fix Enable Synapse Link (#849) 2021-05-28 15:20:19 -05:00
Steve Faulkner
5e2b8d7df0 Upgrade Monaco Editor (#847) 2021-05-28 13:58:35 -05:00
Steve Faulkner
da13a2b3cf Change CI cleanup to once per day 2021-05-28 09:16:36 -05:00
Zachary Foster
69b8196cf0 Removes feature flag from passing masterKey to SDK (#843)
* Remove Feature flag from master key usage

* Adds flag to fallback

* format
2021-05-28 08:12:33 -04:00
Steve Faulkner
5417e1e120 Enable Preview for Hosted Mode (#844) 2021-05-27 22:13:18 -05:00
Steve Faulkner
481ff9e7fe Migrate SidePanel state to Zustand (#799)
Co-authored-by: hardiknai-techm <HN00734461@TechMahindra.com>
2021-05-27 16:07:07 -05:00
Zachary Foster
e41b52e265 Redo user endpoint dynamic token (#827)
* Redo user endpoint dynamic token

* Fixes aad endpoint race condition, tenant switching, and account permissions

* Export const msalInstance

* Format

* fix import

* format

* Redo getMsalInstance

* format again

* Check for doc endpoint
2021-05-27 16:18:44 -04:00
90 changed files with 471 additions and 852 deletions

View File

@@ -111,9 +111,6 @@ src/Explorer/OpenActionsStubs.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
# src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch:
schedule:
# Once every hour
- cron: "0 * * * *"
- cron: "0 15 * * *"
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:

View File

@@ -1,3 +1,4 @@
{
"PROXY_PATH": "/proxy"
"PROXY_PATH": "/proxy",
"msalRedirectURI": "https://cosmos-explorer-preview.azurewebsites.net/"
}

View File

@@ -62,6 +62,17 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
})
.catch(() => res.sendStatus(500));
});
app.get("/", (req, res) => {
fetch("https://api.github.com/repos/Azure/cosmos-explorer/branches/master")
.then((response) => response.json())
.then(({ commit: { sha } }) => {
const explorer = new URL(
"https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/hostedExplorer.html"
);
return res.redirect(explorer.href);
})
.catch(() => res.sendStatus(500));
});
app.listen(port, () => {
console.log(`Example app listening on port: ${port}`);

View File

@@ -83,7 +83,7 @@ export function client(): Cosmos.CosmosClient {
if (_client) return _client;
const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
...(!userContext.features.enableAadDataPlane && { key: userContext.masterKey }),
key: userContext.masterKey,
tokenProvider,
connectionPolicy: {
enableEndpointDiscovery: false,

View File

@@ -28,6 +28,7 @@ export interface ConfigContext {
armAPIVersion?: string;
allowedJunoOrigins: string[];
enableSchemaAnalyzer: boolean;
msalRedirectURI?: string;
}
// Default configuration

View File

@@ -5,7 +5,6 @@ import {
TriggerDefinition,
UserDefinedFunctionDefinition,
} from "@azure/cosmos";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer/Explorer";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
@@ -277,7 +276,6 @@ export interface TabOptions {
title: string;
tabPath: string;
hashLocation: string;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number;

View File

@@ -39,7 +39,6 @@ describe("SettingsComponent", () => {
tabPath: "",
node: undefined,
hashLocation: "settings",
onUpdateTabsButtons: undefined,
}),
};

View File

@@ -18,6 +18,7 @@ import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/genera
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less";
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
@@ -222,13 +223,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.setAutoPilotStates();
this.setBaseline();
if (this.props.settingsTab.isActive()) {
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
}
componentDidUpdate(): void {
if (this.props.settingsTab.isActive()) {
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
}

View File

@@ -31,14 +31,7 @@ exports[`SettingsComponent renders 1`] = `
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -56,7 +49,6 @@ exports[`SettingsComponent renders 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
@@ -81,7 +73,6 @@ exports[`SettingsComponent renders 1`] = `
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
@@ -124,14 +115,7 @@ exports[`SettingsComponent renders 1`] = `
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -149,7 +133,6 @@ exports[`SettingsComponent renders 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
@@ -174,7 +157,6 @@ exports[`SettingsComponent renders 1`] = `
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
@@ -230,14 +212,7 @@ exports[`SettingsComponent renders 1`] = `
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -255,7 +230,6 @@ exports[`SettingsComponent renders 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
@@ -280,7 +254,6 @@ exports[`SettingsComponent renders 1`] = `
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
@@ -323,14 +296,7 @@ exports[`SettingsComponent renders 1`] = `
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -348,7 +314,6 @@ exports[`SettingsComponent renders 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
@@ -373,7 +338,6 @@ exports[`SettingsComponent renders 1`] = `
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {

View File

@@ -4,7 +4,7 @@ import { ThroughputInput } from "./ThroughputInput";
const props = {
isDatabase: false,
showFreeTierExceedThroughputTooltip: true,
isSharded: false,
isSharded: true,
setThroughputValue: () => jest.fn(),
setIsAutoscale: () => jest.fn(),
onCostAcknowledgeChange: () => jest.fn(),

View File

@@ -41,9 +41,16 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
throughputHeaderText = AutoPilotUtils.getAutoPilotHeaderText().toLocaleLowerCase();
} else {
const minRU: string = SharedConstants.CollectionCreation.DefaultCollectionRUs400.toLocaleString();
const maxRU: string = userContext.isTryCosmosDBSubscription
? Constants.TryCosmosExperience.maxRU.toLocaleString()
: "unlimited";
let maxRU: string;
if (userContext.isTryCosmosDBSubscription) {
maxRU = Constants.TryCosmosExperience.maxRU.toLocaleString();
} else if (!isSharded) {
maxRU = "10000";
} else {
maxRU = "unlimited";
}
throughputHeaderText = `throughput (${minRU} - ${maxRU} RU/s)`;
}
return `${isDatabase ? "Database" : getCollectionName()} ${throughputHeaderText}`;

View File

@@ -3,7 +3,7 @@
exports[`ThroughputInput Pane should render Default properly 1`] = `
<ThroughputInput
isDatabase={false}
isSharded={false}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setThroughputValue={[Function]}

View File

@@ -18,6 +18,7 @@ import { configContext, Platform } 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 { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import { RouteHandler } from "../RouteHandlers/RouteHandler";
@@ -34,10 +35,9 @@ 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 { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
import { DialogProps, TextFieldProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent";
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
import { SnapshotRequest } from "./Notebook/NotebookComponent/types";
@@ -53,7 +53,6 @@ import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfir
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
import { GitHubReposPanel } from "./Panes/GitHubReposPanel/GitHubReposPanel";
import { LoadQueryPane } from "./Panes/LoadQueryPane/LoadQueryPane";
import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane/SettingsPane";
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
@@ -81,11 +80,8 @@ BindingHandlersRegisterer.registerBindingHandlers();
var tmp = ComponentRegisterer;
export interface ExplorerParams {
setIsNotificationConsoleExpanded: (isExpanded: boolean) => void;
setNotificationConsoleData: (consoleData: ConsoleData) => void;
setInProgressConsoleDataIdToBeDeleted: (id: string) => void;
openSidePanel: (headerText: string, panelContent: JSX.Element, onClose?: () => void) => void;
closeSidePanel: () => void;
tabsManager: TabsManager;
}
@@ -100,15 +96,9 @@ export default class Explorer {
public tableDataClient: TableDataClient;
public splitter: Splitter;
// Notification Console
private setIsNotificationConsoleExpanded: (isExpanded: boolean) => void;
private setNotificationConsoleData: (consoleData: ConsoleData) => void;
private setInProgressConsoleDataIdToBeDeleted: (id: string) => void;
// Panes
public openSidePanel: (headerText: string, panelContent: JSX.Element, onClose?: () => void) => void;
public closeSidePanel: () => void;
// Resource Tree
public databases: ko.ObservableArray<ViewModels.Database>;
public selectedDatabaseId: ko.Computed<string>;
@@ -153,17 +143,11 @@ export default class Explorer {
content: string;
};
// React adapters
private commandBarComponentAdapter: CommandBarComponentAdapter;
private static readonly MaxNbDatabasesToAutoExpand = 5;
constructor(params?: ExplorerParams) {
this.setIsNotificationConsoleExpanded = params?.setIsNotificationConsoleExpanded;
this.setNotificationConsoleData = params?.setNotificationConsoleData;
this.setInProgressConsoleDataIdToBeDeleted = params?.setInProgressConsoleDataIdToBeDeleted;
this.openSidePanel = params?.openSidePanel;
this.closeSidePanel = params?.closeSidePanel;
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
dataExplorerArea: Constants.Areas.ResourceTree,
@@ -307,7 +291,7 @@ export default class Explorer {
this.tabsManager.openedTabs.subscribe((tabs) => {
if (tabs.length === 0) {
this.selectedNode(undefined);
this.onUpdateTabsButtons([]);
useCommandBar.getState().setContextButtons([]);
}
});
@@ -334,8 +318,6 @@ export default class Explorer {
break;
}
this.commandBarComponentAdapter = new CommandBarComponentAdapter(this);
this._initSettings();
TelemetryProcessor.traceSuccess(
@@ -429,7 +411,7 @@ export default class Explorer {
useDialog.getState().closeDialog();
try {
await update(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.id, {
await update(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, {
properties: {
enableAnalyticalStorage: true,
},
@@ -483,14 +465,6 @@ export default class Explorer {
this.setInProgressConsoleDataIdToBeDeleted(id);
}
public expandConsole(): void {
this.setIsNotificationConsoleExpanded(true);
}
public collapseConsole(): void {
this.setIsNotificationConsoleExpanded(false);
}
public refreshDatabaseForResourceToken(): Q.Promise<any> {
const databaseId = this.resourceTokenDatabaseId();
const collectionId = this.resourceTokenCollectionId();
@@ -806,10 +780,6 @@ export default class Explorer {
);
}
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
this.commandBarComponentAdapter.onUpdateTabsButtons(buttons);
}
private refreshAndExpandNewDatabases(newDatabases: ViewModels.Database[]): Q.Promise<void> {
// we reload collections for all databases so the resource tree reflects any collection-level changes
// i.e addition of stored procedures, etc.
@@ -1101,7 +1071,6 @@ export default class Explorer {
hashLocation: "notebooks",
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
notebookContentItem,
};
@@ -1136,12 +1105,12 @@ export default class Explorer {
if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
} else {
this.openSidePanel(
useSidePanel.getState().openSidePanel(
"Rename Notebook",
<StringInputPane
explorer={this}
closePanel={() => {
this.closeSidePanel();
useSidePanel.getState().closeSidePanel();
this.resourceTree.triggerRender();
}}
inputLabel="Enter new notebook name"
@@ -1167,12 +1136,12 @@ export default class Explorer {
throw new Error(error);
}
this.openSidePanel(
useSidePanel.getState().openSidePanel(
"Create new directory",
<StringInputPane
explorer={this}
closePanel={() => {
this.closeSidePanel();
useSidePanel.getState().closeSidePanel();
this.resourceTree.triggerRender();
}}
errorMessage="Could not create directory "
@@ -1436,7 +1405,6 @@ export default class Explorer {
hashLocation: `${hashLocation} ${index}`,
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
kind: kind,
});
@@ -1467,7 +1435,6 @@ export default class Explorer {
title: title,
tabPath: title,
hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons,
onLoadStartKey: null,
isTabsContentExpanded: ko.observable(true),
},
@@ -1498,7 +1465,7 @@ export default class Explorer {
if (activeTab) {
activeTab.onActivate(); // TODO only update tabs buttons?
} else {
this.onUpdateTabsButtons([]);
useCommandBar.getState().setContextButtons([]);
}
}
@@ -1561,160 +1528,115 @@ export default class Explorer {
return false;
});
}
public openDeleteCollectionConfirmationPane(): void {
this.openSidePanel(
"Delete " + getCollectionName(),
<DeleteCollectionConfirmationPane explorer={this} closePanel={this.closeSidePanel} />
);
useSidePanel
.getState()
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={this} />);
}
public openDeleteDatabaseConfirmationPane(): void {
this.openSidePanel(
"Delete " + getDatabaseName(),
<DeleteDatabaseConfirmationPanel
explorer={this}
openNotificationConsole={() => this.expandConsole()}
closePanel={this.closeSidePanel}
selectedDatabase={this.findSelectedDatabase()}
/>
);
useSidePanel
.getState()
.openSidePanel(
"Delete " + getDatabaseName(),
<DeleteDatabaseConfirmationPanel explorer={this} selectedDatabase={this.findSelectedDatabase()} />
);
}
public openUploadItemsPanePane(): void {
this.openSidePanel("Upload " + getUploadName(), <UploadItemsPane explorer={this} />);
useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane explorer={this} />);
}
public openSettingPane(): void {
this.openSidePanel(
"Setting",
<SettingsPane expandConsole={() => this.expandConsole()} closePanel={this.closeSidePanel} />
);
}
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
this.openSidePanel(
"Input parameters",
<ExecuteSprocParamsPane
storedProcedure={storedProcedure}
expandConsole={() => this.expandConsole()}
closePanel={() => this.closeSidePanel()}
/>
);
useSidePanel
.getState()
.openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />);
}
public async openAddCollectionPanel(databaseId?: string): Promise<void> {
await this.loadDatabaseOffers();
this.openSidePanel(
"New " + getCollectionName(),
<AddCollectionPanel
explorer={this}
closePanel={() => this.closeSidePanel()}
openNotificationConsole={() => this.expandConsole()}
databaseId={databaseId}
/>
);
useSidePanel
.getState()
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
}
public openAddDatabasePane(): void {
this.openSidePanel(
"New " + getDatabaseName(),
<AddDatabasePanel
explorer={this}
openNotificationConsole={() => this.expandConsole()}
closePanel={this.closeSidePanel}
/>
);
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this} />);
}
public openBrowseQueriesPanel(): void {
this.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} closePanel={this.closeSidePanel} />);
}
public openLoadQueryPanel(): void {
this.openSidePanel("Load Query", <LoadQueryPane explorer={this} closePanel={() => this.closeSidePanel()} />);
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} />);
}
public openSaveQueryPanel(): void {
this.openSidePanel("Save Query", <SaveQueryPane explorer={this} closePanel={() => this.closeSidePanel()} />);
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this} />);
}
public openUploadFilePanel(parent?: NotebookContentItem): void {
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.openSidePanel(
"Upload file to notebook server",
<UploadFilePane
expandConsole={() => this.expandConsole()}
closePanel={this.closeSidePanel}
uploadFile={(name: string, content: string) => this.uploadFile(name, content, parent)}
/>
);
useSidePanel
.getState()
.openSidePanel(
"Upload file to notebook server",
<UploadFilePane uploadFile={(name: string, content: string) => this.uploadFile(name, content, parent)} />
);
}
public openCassandraAddCollectionPane(): void {
this.openSidePanel(
"Add Table",
<CassandraAddCollectionPane
explorer={this}
closePanel={() => this.closeSidePanel()}
cassandraApiClient={new CassandraAPIDataClient()}
/>
);
useSidePanel
.getState()
.openSidePanel(
"Add Table",
<CassandraAddCollectionPane explorer={this} cassandraApiClient={new CassandraAPIDataClient()} />
);
}
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
this.openSidePanel(
header,
<GitHubReposPanel
explorer={this}
closePanel={this.closeSidePanel}
gitHubClientProp={this.notebookManager.gitHubClient}
junoClientProp={junoClient}
openNotificationConsole={() => this.expandConsole()}
/>
);
useSidePanel
.getState()
.openSidePanel(
header,
<GitHubReposPanel
explorer={this}
gitHubClientProp={this.notebookManager.gitHubClient}
junoClientProp={junoClient}
/>
);
}
public openAddTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void {
this.openSidePanel(
"Add Table Entity",
<AddTableEntityPanel
explorer={this}
closePanel={this.closeSidePanel}
queryTablesTab={queryTablesTab}
tableEntityListViewModel={tableEntityListViewModel}
cassandraApiClient={new CassandraAPIDataClient()}
/>
);
useSidePanel
.getState()
.openSidePanel(
"Add Table Entity",
<AddTableEntityPanel
tableDataClient={this.tableDataClient}
queryTablesTab={queryTablesTab}
tableEntityListViewModel={tableEntityListViewModel}
cassandraApiClient={new CassandraAPIDataClient()}
/>
);
}
public openSetupNotebooksPanel(title: string, description: string): void {
this.openSidePanel(
title,
<SetupNoteBooksPanel
explorer={this}
closePanel={this.closeSidePanel}
openNotificationConsole={() => this.expandConsole()}
panelTitle={title}
panelDescription={description}
/>
);
useSidePanel
.getState()
.openSidePanel(title, <SetupNoteBooksPanel explorer={this} panelTitle={title} panelDescription={description} />);
}
public openEditTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void {
this.openSidePanel(
"Edit Table Entity",
<EditTableEntityPanel
explorer={this}
closePanel={this.closeSidePanel}
queryTablesTab={queryTablesTab}
tableEntityListViewModel={tableEntityListViewModel}
cassandraApiClient={new CassandraAPIDataClient()}
/>
);
useSidePanel
.getState()
.openSidePanel(
"Edit Table Entity",
<EditTableEntityPanel
tableDataClient={this.tableDataClient}
queryTablesTab={queryTablesTab}
tableEntityListViewModel={tableEntityListViewModel}
cassandraApiClient={new CassandraAPIDataClient()}
/>
);
}
public openTableSelectQueryPanel(queryViewModal: QueryViewModel): void {
this.openSidePanel(
"Select Column",
<TableQuerySelectPanel explorer={this} closePanel={this.closeSidePanel} queryViewModel={queryViewModal} />
);
useSidePanel.getState().openSidePanel("Select Column", <TableQuerySelectPanel queryViewModel={queryViewModal} />);
}
public openSettingPane(): void {
useSidePanel.getState().openSidePanel("Settings", <SettingsPane />);
}
}

View File

@@ -3,100 +3,71 @@
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import { CommandBar, ICommandBarItemProps } from "@fluentui/react";
import * as ko from "knockout";
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import create, { UseStore } from "zustand";
import { StyleConstants } from "../../../Common/Constants";
import * as ViewModels from "../../../Contracts/ViewModels";
import { userContext } from "../../../UserContext";
import { useObservable } from "../../../hooks/useObservable";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
import * as CommandBarUtil from "./CommandBarUtil";
export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
public container: Explorer;
private tabsButtons: CommandButtonComponentProps[];
private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: Explorer) {
this.container = container;
this.tabsButtons = [];
this.isNotebookTabActive = ko.computed(
() => container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2
);
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [
container.isDatabaseNodeOrNoneSelected,
container.isDatabaseNodeSelected,
container.isNoneSelected,
container.isResourceTokenCollectionNodeSelected,
container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating,
userContext?.databaseAccount,
this.isNotebookTabActive,
container.isServerlessEnabled,
];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
this.parameters = ko.observable(Date.now());
}
public onUpdateTabsButtons(buttons: CommandButtonComponentProps[]): void {
this.tabsButtons = buttons;
this.triggerRender();
}
public renderComponent(): JSX.Element {
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(this.container);
const contextButtons = (this.tabsButtons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(this.container)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(this.container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (this.tabsButtons && this.tabsButtons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (this.isNotebookTabActive()) {
uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
);
}
return (
<React.Fragment>
<div className="commandBarContainer">
<CommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
</React.Fragment>
);
}
private triggerRender() {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
interface Props {
container: Explorer;
}
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
}));
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
useObservable(container.selectedNode);
const buttons = useCommandBar((state) => state.contextButtons);
const backgroundColor = StyleConstants.BaseLight;
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container);
const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container)
);
const controlButtons = CommandBarComponentButtonFactory.createControlCommandBarButtons(container);
const uiFabricStaticButtons = CommandBarUtil.convertButton(staticButtons, backgroundColor);
if (buttons && buttons.length > 0) {
uiFabricStaticButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
}
const uiFabricTabsButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(contextButtons, backgroundColor);
if (uiFabricTabsButtons.length > 0) {
uiFabricStaticButtons.push(CommandBarUtil.createDivider("commandBarDivider"));
}
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker", container.memoryUsageInfo));
}
return (
<div className="commandBarContainer">
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
farItems={uiFabricControlButtons}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
);
};

View File

@@ -21,11 +21,13 @@ import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { OpenFullScreen } from "../../OpenFullScreen";
import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane";
let counter = 0;
@@ -152,7 +154,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
{
iconSrc: SettingsIcon,
iconAlt: "Settings",
onCommandClick: () => container.openSettingPane(),
onCommandClick: container.openSettingPane,
commandButtonLabel: undefined,
ariaLabel: "Settings",
tooltipText: "Settings",
@@ -167,7 +169,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
iconSrc: OpenInTabIcon,
iconAlt: label,
onCommandClick: () => {
container.openSidePanel("Open Full Screen", <OpenFullScreen />);
useSidePanel.getState().openSidePanel("Open Full Screen", <OpenFullScreen />);
},
commandButtonLabel: undefined,
ariaLabel: label,
@@ -408,7 +410,7 @@ function createOpenQueryFromDiskButton(container: Explorer): CommandButtonCompon
return {
iconSrc: OpenQueryFromDiskIcon,
iconAlt: label,
onCommandClick: () => container.openLoadQueryPanel(),
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane explorer={container} />),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,

View File

@@ -15,6 +15,7 @@ import LoadingIcon from "../../../../images/loading.svg";
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
import { userContext } from "../../../UserContext";
/**
@@ -321,3 +322,23 @@ const PrPreview = (props: { pr: string }) => {
</>
);
};
export const NotificationConsole: React.FC<
Pick<NotificationConsoleComponentProps, "consoleData" | "inProgressConsoleDataIdToBeDeleted">
> = ({
consoleData,
inProgressConsoleDataIdToBeDeleted,
}: Pick<NotificationConsoleComponentProps, "consoleData" | "inProgressConsoleDataIdToBeDeleted">) => {
const setIsExpanded = useNotificationConsole((state) => state.setIsExpanded);
const isExpanded = useNotificationConsole((state) => state.isExpanded);
// TODO Refactor NotificationConsoleComponent into a functional component and remove this wrapper
// This component only exists so we can use hooks and pass them down to a non-functional component
return (
<NotificationConsoleComponent
consoleData={consoleData}
inProgressConsoleDataIdToBeDeleted={inProgressConsoleDataIdToBeDeleted}
isConsoleExpanded={isExpanded}
setIsConsoleExpanded={setIsExpanded}
/>
);
};

View File

@@ -14,13 +14,13 @@ import { MemoryUsageInfo } from "../../Contracts/DataModels";
import { GitHubClient } from "../../GitHub/GitHubClient";
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
import { useSidePanel } from "../../hooks/useSidePanel";
import { JunoClient } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { getFullName } from "../../Utils/UserUtils";
import Explorer from "../Explorer";
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
@@ -56,8 +56,6 @@ export default class NotebookManager {
public gitHubOAuthService: GitHubOAuthService;
public gitHubClient: GitHubClient;
public gitHubReposPane: ContextualPaneBase;
public initialize(params: NotebookManagerOptions): void {
this.params = params;
this.junoClient = new JunoClient();
@@ -98,7 +96,7 @@ export default class NotebookManager {
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
this.gitHubClient.setToken(token?.access_token);
if (this?.gitHubOAuthService.isLoggedIn()) {
this.params.container.closeSidePanel();
useSidePanel.getState().closeSidePanel();
this.params.container.openGitHubReposPanel("Manager GitHub settings", this.junoClient);
}
@@ -127,37 +125,37 @@ export default class NotebookManager {
onTakeSnapshot: (request: SnapshotRequest) => void,
onClosePanel: () => void
): Promise<void> {
const explorer = this.params.container;
explorer.openSidePanel(
"Publish Notebook",
<PublishNotebookPane
explorer={this.params.container}
junoClient={this.junoClient}
closePanel={this.params.container.closeSidePanel}
openNotificationConsole={this.params.container.expandConsole}
name={name}
author={getFullName()}
notebookContent={content}
notebookContentRef={notebookContentRef}
onTakeSnapshot={onTakeSnapshot}
/>,
onClosePanel
);
useSidePanel
.getState()
.openSidePanel(
"Publish Notebook",
<PublishNotebookPane
explorer={this.params.container}
junoClient={this.junoClient}
name={name}
author={getFullName()}
notebookContent={content}
notebookContentRef={notebookContentRef}
onTakeSnapshot={onTakeSnapshot}
/>,
onClosePanel
);
}
public openCopyNotebookPane(name: string, content: string): void {
const { container } = this.params;
container.openSidePanel(
"Copy Notebook",
<CopyNotebookPane
container={container}
closePanel={container.closeSidePanel}
junoClient={this.junoClient}
gitHubOAuthService={this.gitHubOAuthService}
name={name}
content={content}
/>
);
useSidePanel
.getState()
.openSidePanel(
"Copy Notebook",
<CopyNotebookPane
container={container}
junoClient={this.junoClient}
gitHubOAuthService={this.gitHubOAuthService}
name={name}
content={content}
/>
);
}
// Octokit's error handler uses any

View File

@@ -20,6 +20,7 @@ import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"
import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import { useSidePanel } from "../../hooks/useSidePanel";
import { CollectionCreation } from "../../Shared/Constants";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -36,8 +37,6 @@ import { PanelLoadingScreen } from "./PanelLoadingScreen";
export interface AddCollectionPanelProps {
explorer: Explorer;
closePanel: () => void;
openNotificationConsole: () => void;
databaseId?: string;
}
@@ -133,7 +132,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
message={this.state.errorMessage}
messageType="error"
showErrorDetails={this.state.showErrorDetails}
openNotificationConsole={this.props.openNotificationConsole}
/>
)}
@@ -142,7 +140,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
message={getUpsellMessage(userContext.portalEnv, true, this.props.explorer.isFirstResourceCreated(), true)}
messageType="info"
showErrorDetails={false}
openNotificationConsole={this.props.openNotificationConsole}
link={Constants.Urls.freeTierInformation}
linkText="Learn more"
/>
@@ -1062,7 +1059,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.setState({ isExecuting: false });
this.props.explorer.refreshAllDatabases();
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
this.props.closePanel();
useSidePanel.getState().closeSidePanel();
} catch (error) {
const errorMessage: string = getErrorMessage(error);
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });

View File

@@ -6,6 +6,7 @@ import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUti
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import * as DataModels from "../../../Contracts/DataModels";
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
import { useSidePanel } from "../../../hooks/useSidePanel";
import * as SharedConstants from "../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -20,15 +21,12 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
export interface AddDatabasePaneProps {
explorer: Explorer;
closePanel: () => void;
openNotificationConsole: () => void;
}
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
explorer: container,
closePanel,
openNotificationConsole,
}: AddDatabasePaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
let throughput: number;
let isAutoscaleSelected: boolean;
let isCostAcknowledged: boolean;
@@ -114,7 +112,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
const _onCreateDatabaseSuccess = (offerThroughput: number, startKey: number): void => {
setIsExecuting(false);
closePanel();
closeSidePanel();
container.refreshAllDatabases();
const addDatabasePaneSuccessMessage = {
...addDatabasePaneMessage,
@@ -163,7 +161,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
);
const props: RightPaneFormProps = {
expandConsole: openNotificationConsole,
formError: formErrors,
isExecuting,
submitButtonText: "OK",
@@ -177,7 +174,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
message={getUpsellMessage(userContext.portalEnv, true, container.isFirstResourceCreated(), true)}
messageType="info"
showErrorDetails={false}
openNotificationConsole={openNotificationConsole}
link={Constants.Urls.freeTierInformation}
linkText="Learn more"
/>

View File

@@ -2,7 +2,6 @@
exports[`AddDatabasePane Pane should render Default properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -3,6 +3,7 @@ import { Areas } from "../../../Common/Constants";
import { logError } from "../../../Common/Logger";
import { Query } from "../../../Contracts/DataModels";
import { Collection } from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
@@ -15,13 +16,12 @@ import QueryTab from "../../Tabs/QueryTab";
interface BrowseQueriesPaneProps {
explorer: Explorer;
closePanel: () => void;
}
export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
explorer,
closePanel,
}: BrowseQueriesPaneProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const loadSavedQuery = (savedQuery: Query): void => {
const selectedCollection: Collection = explorer && explorer.findSelectedCollection();
if (!selectedCollection) {
@@ -43,7 +43,7 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
queryName: savedQuery.queryName,
paneTitle: "Open Saved Queries",
});
closePanel();
closeSidePanel();
};
const props: QueriesGridComponentProps = {

View File

@@ -6,6 +6,7 @@ import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUti
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import * as AddCollectionUtility from "../../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
@@ -19,15 +20,14 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
export interface CassandraAddCollectionPaneProps {
explorer: Explorer;
closePanel: () => void;
cassandraApiClient: CassandraAPIDataClient;
}
export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectionPaneProps> = ({
explorer: container,
closePanel,
cassandraApiClient,
}: CassandraAddCollectionPaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const throughputDefaults = userContext.collectionCreationDefaults.throughput;
const [createTableQuery, setCreateTableQuery] = useState<string>("CREATE TABLE ");
const [keyspaceId, setKeyspaceId] = useState<string>("");
@@ -241,7 +241,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
}
container.refreshAllDatabases();
setIsExecuting(false);
closePanel();
closeSidePanel();
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey);
} catch (error) {
@@ -261,7 +261,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
};
const props: RightPaneFormProps = {
expandConsole: () => container.expandConsole(),
formError: formErrors,
isExecuting,
submitButtonText: "Apply",

View File

@@ -2,7 +2,6 @@
exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
onSubmit={[Function]}
submitButtonText="Apply"

View File

@@ -1,126 +0,0 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { KeyCodes } from "../../Common/Constants";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
// TODO: Use specific actions for logging telemetry data
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel {
private initalFocusedElement: HTMLElement | undefined;
public id: string;
public container: Explorer;
public firstFieldHasFocus: ko.Observable<boolean>;
public formErrorsDetails: ko.Observable<string>;
public formErrors: ko.Observable<string>;
public title: ko.Observable<string>;
public visible: ko.Observable<boolean>;
public isExecuting: ko.Observable<boolean>;
constructor(options: ViewModels.PaneOptions) {
super();
this.id = options.id;
this.container = options.container;
this.visible = options.visible || ko.observable(false);
this.firstFieldHasFocus = ko.observable<boolean>(false);
this.formErrors = ko.observable<string>();
this.title = ko.observable<string>();
this.formErrorsDetails = ko.observable<string>();
this.isExecuting = ko.observable<boolean>(false);
}
public cancel() {
this.close();
this.container.isAccountReady() &&
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Close, {
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
});
}
public close() {
this.visible(false);
this.isExecuting(false);
this.resetData();
this.resetFocus();
}
public open() {
this.initalFocusedElement = document.activeElement as HTMLElement;
this.visible(true);
this.firstFieldHasFocus(true);
this.resizePane();
this.container.isAccountReady() &&
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Open, {
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
});
}
public resetData() {
this.firstFieldHasFocus(false);
this.formErrors(null);
this.formErrorsDetails(null);
}
public showErrorDetails() {
this.container.expandConsole();
}
public submit() {
this.close();
event.stopPropagation();
this.container.isAccountReady() &&
TelemetryProcessor.trace(Action.ContextualPane, ActionModifiers.Submit, {
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
});
}
public onCloseKeyPress(source: any, event: KeyboardEvent): boolean {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.close();
event.stopPropagation();
return false;
}
return true;
}
public onPaneKeyDown(source: any, event: KeyboardEvent): boolean {
if (event.keyCode === KeyCodes.Escape) {
this.close();
event.stopPropagation();
return false;
}
return true;
}
public onSubmitKeyPress(source: any, event: KeyboardEvent): boolean {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.submit();
event.stopPropagation();
return false;
}
return true;
}
private resizePane(): void {
const paneElement: HTMLElement = document.getElementById(this.id);
const notificationConsoleElement: HTMLElement = document.getElementById("explorerNotificationConsole");
const newPaneElementHeight = window.innerHeight - $(notificationConsoleElement).height();
$(paneElement).height(newPaneElementHeight);
}
private resetFocus(): void {
if (this.initalFocusedElement) {
this.initalFocusedElement.focus();
this.initalFocusedElement = undefined;
}
}
}

View File

@@ -3,6 +3,7 @@ import React, { FormEvent, FunctionComponent, useEffect, useState } from "react"
import { HttpStatusCodes } from "../../../Common/Constants";
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
@@ -26,7 +27,6 @@ export interface CopyNotebookPanelProps {
container: Explorer;
junoClient: JunoClient;
gitHubOAuthService: GitHubOAuthService;
closePanel: () => void;
}
export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
@@ -35,8 +35,8 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
container,
junoClient,
gitHubOAuthService,
closePanel,
}: CopyNotebookPanelProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isExecuting, setIsExecuting] = useState<boolean>();
const [formError, setFormError] = useState<string>("");
const [pinnedRepos, setPinnedRepos] = useState<IPinnedRepo[]>();
@@ -84,7 +84,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
}
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${name} to ${destination}`);
closePanel();
closeSidePanel();
} catch (error) {
const errorMessage = getErrorMessage(error);
setFormError(`Failed to copy ${name} to ${destination}`);
@@ -130,7 +130,6 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
isExecuting: isExecuting,
submitButtonText: "OK",
onSubmit: () => submit(),
expandConsole: () => container.expandConsole(),
};
const copyNotebookPaneProps: CopyNotebookPaneProps = {

View File

@@ -5,6 +5,7 @@ import { deleteCollection } from "../../../Common/dataAccess/deleteCollection";
import DeleteFeedback from "../../../Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { Collection } from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -15,13 +16,12 @@ import Explorer from "../../Explorer";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface DeleteCollectionConfirmationPaneProps {
explorer: Explorer;
closePanel: () => void;
}
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
explorer,
closePanel,
}: DeleteCollectionConfirmationPaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
const [inputCollectionName, setInputCollectionName] = useState<string>("");
const [formError, setFormError] = useState<string>("");
@@ -79,7 +79,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
});
}
closePanel();
closeSidePanel();
} catch (error) {
const errorMessage = getErrorMessage(error);
@@ -102,7 +102,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
isExecuting,
submitButtonText: "OK",
onSubmit,
expandConsole: () => explorer.expandConsole(),
};
return (
<RightPaneForm {...props}>

View File

@@ -16,7 +16,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
}
>
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -6,6 +6,7 @@ import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
import DeleteFeedback from "../../Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { Collection, Database } from "../../Contracts/ViewModels";
import { useSidePanel } from "../../hooks/useSidePanel";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -17,17 +18,14 @@ import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm
interface DeleteDatabaseConfirmationPanelProps {
explorer: Explorer;
closePanel: () => void;
openNotificationConsole: () => void;
selectedDatabase: Database;
}
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
explorer,
openNotificationConsole,
closePanel,
selectedDatabase,
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
const [formError, setFormError] = useState<string>("");
@@ -51,7 +49,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
try {
await deleteDatabase(selectedDatabase.id());
closePanel();
closeSidePanel();
explorer.refreshAllDatabases();
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
explorer.selectedNode(undefined);
@@ -111,7 +109,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
isExecuting: isLoading,
submitButtonText: "OK",
onSubmit: () => submit(),
expandConsole: openNotificationConsole,
};
const errorProps: PanelInfoErrorProps = {

View File

@@ -2,15 +2,14 @@ import { IDropdownOption, IImageProps, Image, Stack, Text } from "@fluentui/reac
import { useBoolean } from "@fluentui/react-hooks";
import React, { FunctionComponent, useState } from "react";
import AddPropertyIcon from "../../../../images/Add-property.svg";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import StoredProcedure from "../../Tree/StoredProcedure";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
import { InputParameter } from "./InputParameter";
interface ExecuteSprocParamsPaneProps {
expandConsole: () => void;
storedProcedure: StoredProcedure;
closePanel: () => void;
}
const imageProps: IImageProps = {
@@ -24,10 +23,9 @@ interface UnwrappedExecuteSprocParam {
}
export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPaneProps> = ({
expandConsole,
storedProcedure,
closePanel,
}: ExecuteSprocParamsPaneProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
const [paramKeyValues, setParamKeyValues] = useState<UnwrappedExecuteSprocParam[]>([{ key: "string", text: "" }]);
const [partitionValue, setPartitionValue] = useState<string>(); // Defaulting to undefined here is important. It is not the same partition key as ""
@@ -76,7 +74,7 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
});
storedProcedure.execute(sprocParams, partitionKey === "custom" ? JSON.parse(partitionValue) : partitionValue);
setLoadingFalse();
closePanel();
closeSidePanel();
};
const deleteParamAtIndex = (indexToRemove: number): void => {
@@ -114,7 +112,6 @@ export const ExecuteSprocParamsPane: FunctionComponent<ExecuteSprocParamsPanePro
};
const props: RightPaneFormProps = {
expandConsole,
formError: formError,
isExecuting: isLoading,
submitButtonText: "Execute",

View File

@@ -7,7 +7,6 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
storedProcedure={Object {}}
>
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -2,6 +2,7 @@ import React from "react";
import { Areas, HttpStatusCodes } from "../../../Common/Constants";
import { handleError } from "../../../Common/ErrorHandlingUtils";
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../../GitHub/GitHubClient";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -21,10 +22,8 @@ import { PanelLoadingScreen } from "../PanelLoadingScreen";
interface IGitHubReposPanelProps {
explorer: Explorer;
closePanel: () => void;
gitHubClientProp: GitHubClient;
junoClientProp: JunoClient;
openNotificationConsole: () => void;
}
interface IGitHubReposPanelState {
@@ -91,7 +90,7 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
},
resetConnection: (): void => this.setup(true),
onOkClick: (): Promise<void> => this.submit(),
onCancelClick: (): void => this.props.closePanel(),
onCancelClick: (): void => useSidePanel.getState().closeSidePanel(),
},
};
this.gitHubClient = this.props.gitHubClientProp;
@@ -439,7 +438,6 @@ export class GitHubReposPanel extends React.Component<IGitHubReposPanelProps, IG
message={this.state.errorMessage}
messageType="error"
showErrorDetails={this.state.showErrorDetails}
openNotificationConsole={this.props.openNotificationConsole}
/>
)}
<div className="panelMainContent" style={ContentMainStyle}>

View File

@@ -20,14 +20,7 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -45,7 +38,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
@@ -70,7 +62,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {

View File

@@ -1,25 +1,27 @@
import React, { FunctionComponent } from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { GraphStyleComponent } from "../../Graph/GraphStyleComponent/GraphStyleComponent";
import { IGraphConfig } from "../../Tabs/GraphTab";
import { PanelFooterComponent } from "../PanelFooterComponent";
interface GraphStylingProps {
closePanel: () => void;
igraphConfigUiData: ViewModels.IGraphConfigUiData;
igraphConfig: IGraphConfig;
getValues: (igraphConfig?: IGraphConfig) => void;
}
export const GraphStylingPanel: FunctionComponent<GraphStylingProps> = ({
closePanel,
igraphConfigUiData,
igraphConfig,
getValues,
}: GraphStylingProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const buttonLabel = "Ok";
const submit = () => {
closePanel();
const submit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
closeSidePanel();
};
return (

View File

@@ -4,6 +4,7 @@ import React, { FunctionComponent, useState } from "react";
import folderIcon from "../../../../images/folder_16x16.svg";
import { logError } from "../../../Common/Logger";
import { Collection } from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
@@ -12,13 +13,10 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
interface LoadQueryPaneProps {
explorer: Explorer;
closePanel: () => void;
}
export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
explorer,
closePanel,
}: LoadQueryPaneProps): JSX.Element => {
export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({ explorer }: LoadQueryPaneProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
const [formError, setFormError] = useState<string>("");
const [selectedFileName, setSelectedFileName] = useState<string>("");
@@ -51,7 +49,7 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
try {
await loadQueryFromFile(file);
logConsoleInfo(`Successfully loaded query from file ${file.name}`);
closePanel();
closeSidePanel();
setLoadingFalse();
} catch (error) {
setLoadingFalse();
@@ -89,7 +87,6 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({
isExecuting: isLoading,
submitButtonText: "Load",
onSubmit: () => submit(),
expandConsole: () => explorer.expandConsole(),
};
return (

View File

@@ -2,7 +2,6 @@
exports[`Load Query Pane should render Default properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -36,14 +36,7 @@ describe("New Vertex Panel", () => {
it("should call form submit method", () => {
const onSubmitSpy = jest.fn();
const newWrapper = mount(
<NewVertexPanel
explorer={fakeExplorer}
partitionKeyPropertyProp={undefined}
openNotificationConsole={(): void => undefined}
onSubmit={onSubmitSpy}
/>
);
const newWrapper = mount(<NewVertexPanel partitionKeyPropertyProp={undefined} onSubmit={onSubmitSpy} />);
//eslint-disable-next-line
newWrapper.find("form").simulate("submit", { preventDefault: () => {} });
@@ -61,14 +54,7 @@ describe("New Vertex Panel", () => {
const result = onSubmitSpy(fakeNewVertexData, onErrorSpy, onSuccessSpy);
const newWrapper = mount(
<NewVertexPanel
explorer={fakeExplorer}
partitionKeyPropertyProp={undefined}
openNotificationConsole={(): void => undefined}
onSubmit={onSubmitSpy}
/>
);
const newWrapper = mount(<NewVertexPanel partitionKeyPropertyProp={undefined} onSubmit={onSubmitSpy} />);
//eslint-disable-next-line
newWrapper.find("form").simulate("submit", { preventDefault: () => {} });

View File

@@ -1,21 +1,17 @@
import { useBoolean } from "@fluentui/react-hooks";
import React, { FunctionComponent, useState } from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import Explorer from "../../Explorer";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { NewVertexComponent } from "../../Graph/NewVertexComponent/NewVertexComponent";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface INewVertexPanelProps {
explorer: Explorer;
partitionKeyPropertyProp: string;
onSubmit: (result: ViewModels.NewVertexData, onError: (errorMsg: string) => void, onSuccess: () => void) => void;
openNotificationConsole: () => void;
}
export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
explorer,
partitionKeyPropertyProp,
onSubmit,
openNotificationConsole,
}: INewVertexPanelProps): JSX.Element => {
let newVertexDataValue: ViewModels.NewVertexData;
const [errorMessage, setErrorMessage] = useState<string>("");
@@ -33,10 +29,10 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
setErrorMessage(errorMsg);
setLoadingFalse();
};
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const onSuccess = () => {
setLoadingFalse();
explorer.closeSidePanel();
closeSidePanel();
};
const onChange = (newVertexData: ViewModels.NewVertexData) => {
@@ -47,7 +43,6 @@ export const NewVertexPanel: FunctionComponent<INewVertexPanelProps> = ({
isExecuting: isLoading,
submitButtonText: "OK",
onSubmit: () => submit(),
expandConsole: openNotificationConsole,
};
return (

View File

@@ -2,7 +2,6 @@
exports[`New Vertex Panel should render default property 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -9,7 +9,6 @@ describe("PaneContainerComponent test", () => {
panelContent: <div></div>,
isOpen: true,
isConsoleExpanded: false,
closePanel: undefined,
};
const wrapper = shallow(<PanelContainerComponent {...panelContainerProps} />);
expect(wrapper).toMatchSnapshot();
@@ -21,7 +20,6 @@ describe("PaneContainerComponent test", () => {
panelContent: undefined,
isOpen: true,
isConsoleExpanded: false,
closePanel: undefined,
};
const wrapper = shallow(<PanelContainerComponent {...panelContainerProps} />);
expect(wrapper).toMatchSnapshot();
@@ -33,7 +31,6 @@ describe("PaneContainerComponent test", () => {
panelContent: <div></div>,
isOpen: true,
isConsoleExpanded: true,
closePanel: undefined,
};
const wrapper = shallow(<PanelContainerComponent {...panelContainerProps} />);
expect(wrapper).toMatchSnapshot();

View File

@@ -1,12 +1,13 @@
import { IPanelProps, IRenderFunction, Panel, PanelType } from "@fluentui/react";
import * as React from "react";
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { useSidePanel } from "../../hooks/useSidePanel";
export interface PanelContainerProps {
headerText: string;
panelContent: JSX.Element;
isConsoleExpanded: boolean;
isOpen: boolean;
closePanel: () => void;
panelWidth?: string;
onRenderNavigationContent?: IRenderFunction<IPanelProps>;
}
@@ -69,7 +70,7 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
if ((ev.target as HTMLElement).id === "notificationConsoleHeader") {
ev.preventDefault();
} else {
this.props.closePanel();
useSidePanel.getState().closeSidePanel();
}
};
@@ -81,3 +82,24 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
return panelHeight + "px";
};
}
export const SidePanel: React.FC = () => {
const isConsoleExpanded = useNotificationConsole((state) => state.isExpanded);
const { isOpen, panelContent, headerText } = useSidePanel((state) => {
return {
isOpen: state.isOpen,
panelContent: state.panelContent,
headerText: state.headerText,
};
});
// TODO Refactor PanelContainerComponent into a functional component and remove this wrapper
// This component only exists so we can use hooks and pass them down to a non-functional component
return (
<PanelContainerComponent
isOpen={isOpen}
panelContent={panelContent}
headerText={headerText}
isConsoleExpanded={isConsoleExpanded}
/>
);
};

View File

@@ -1,5 +1,6 @@
import { Icon, Link, Stack, Text } from "@fluentui/react";
import React from "react";
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
export interface PanelInfoErrorProps {
message: string;
@@ -7,7 +8,6 @@ export interface PanelInfoErrorProps {
showErrorDetails: boolean;
link?: string;
linkText?: string;
openNotificationConsole?: () => void;
formError?: boolean;
}
@@ -17,8 +17,9 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
showErrorDetails,
link,
linkText,
openNotificationConsole,
}: PanelInfoErrorProps): JSX.Element => {
const expandConsole = useNotificationConsole((state) => state.expandConsole);
let icon: JSX.Element = <Icon iconName="InfoSolid" className="panelLargeInfoIcon" aria-label="Infomation" />;
if (messageType === "error") {
icon = <Icon iconName="StatusErrorFull" className="panelErrorIcon" aria-label="error" />;
@@ -41,7 +42,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
)}
</Text>
{showErrorDetails && (
<a className="paneErrorLink" role="link" onClick={openNotificationConsole}>
<a className="paneErrorLink" role="link" onClick={expandConsole}>
More details
</a>
)}

View File

@@ -3,6 +3,7 @@ import React, { FunctionComponent, useEffect, useState } from "react";
import { HttpStatusCodes } from "../../../Common/Constants";
import { getErrorMessage, getErrorStack, handleError } from "../../../Common/ErrorHandlingUtils";
import { useNotebookSnapshotStore } from "../../../hooks/useNotebookSnapshotStore";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { JunoClient } from "../../../Juno/JunoClient";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -17,8 +18,6 @@ import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./Publis
export interface PublishNotebookPaneAProps {
explorer: Explorer;
closePanel: () => void;
openNotificationConsole: () => void;
junoClient: JunoClient;
name: string;
author: string;
@@ -29,13 +28,14 @@ export interface PublishNotebookPaneAProps {
export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> = ({
explorer: container,
junoClient,
closePanel,
name,
author,
notebookContent,
notebookContentRef,
onTakeSnapshot,
}: PublishNotebookPaneAProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isCodeOfConductAccepted, setIsCodeOfConductAccepted] = useState<boolean>(false);
const [content, setContent] = useState<string>("");
const [formError, setFormError] = useState<string>("");
@@ -152,7 +152,7 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
clearPublishingMessage();
setIsExecuting(false);
}
closePanel();
closeSidePanel();
};
const createFormError = (formError: string, formErrorDetail: string, area: string): void => {
@@ -171,7 +171,6 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
isExecuting: isExecuting,
submitButtonText: "Publish",
onSubmit: () => submit(),
expandConsole: () => container.expandConsole(),
isSubmitButtonHidden: !isCodeOfConductAccepted,
};

View File

@@ -4,7 +4,6 @@ import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { PanelLoadingScreen } from "../PanelLoadingScreen";
export interface RightPaneFormProps {
expandConsole: () => void;
formError: string;
isExecuting: boolean;
onSubmit: () => void;
@@ -14,7 +13,6 @@ export interface RightPaneFormProps {
}
export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
expandConsole,
formError,
isExecuting,
onSubmit,
@@ -30,14 +28,7 @@ export const RightPaneForm: FunctionComponent<RightPaneFormProps> = ({
return (
<>
<form className="panelFormWrapper" onSubmit={handleOnSubmit}>
{formError && (
<PanelInfoErrorComponent
messageType="error"
message={formError}
showErrorDetails={true}
openNotificationConsole={expandConsole}
/>
)}
{formError && <PanelInfoErrorComponent messageType="error" message={formError} showErrorDetails={true} />}
{children}
{!isSubmitButtonHidden && <PanelFooterComponent buttonLabel={submitButtonText} />}
</form>

View File

@@ -4,6 +4,7 @@ import React, { FunctionComponent, useState } from "react";
import { Areas, SavedQueries } from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { Query } from "../../../Contracts/DataModels";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
@@ -13,13 +14,10 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
interface SaveQueryPaneProps {
explorer: Explorer;
closePanel: () => void;
}
export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
explorer,
closePanel,
}: SaveQueryPaneProps): JSX.Element => {
export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer }: SaveQueryPaneProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
const [formError, setFormError] = useState<string>("");
const [queryName, setQueryName] = useState<string>("");
@@ -71,7 +69,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
},
startKey
);
closePanel();
closeSidePanel();
} catch (error) {
setLoadingFalse();
const errorMessage = getErrorMessage(error);
@@ -128,7 +126,6 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
};
const props: RightPaneFormProps = {
expandConsole: () => explorer.expandConsole(),
formError: formError,
isExecuting: isLoading,
submitButtonText: canSaveQueries() ? "Save" : "Complete setup",

View File

@@ -2,7 +2,6 @@
exports[`Save Query Pane should render Default properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -3,13 +3,10 @@ import React from "react";
import { DatabaseAccount } from "../../../Contracts/DataModels";
import { updateUserContext } from "../../../UserContext";
import { SettingsPane } from "./SettingsPane";
const props = {
expandConsole: (): void => undefined,
closePanel: (): void => undefined,
};
describe("Settings Pane", () => {
it("should render Default properly", () => {
const wrapper = shallow(<SettingsPane {...props} />);
const wrapper = shallow(<SettingsPane />);
expect(wrapper).toMatchSnapshot();
});
@@ -21,7 +18,7 @@ describe("Settings Pane", () => {
},
} as DatabaseAccount,
});
const wrapper = shallow(<SettingsPane {...props} />);
const wrapper = shallow(<SettingsPane />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -3,21 +3,15 @@ import React, { FunctionComponent, MouseEvent, useState } from "react";
import * as Constants from "../../../Common/Constants";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import { configContext } from "../../../ConfigContext";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
import * as StringUtility from "../../../Shared/StringUtility";
import { userContext } from "../../../UserContext";
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface SettingsPaneProps {
expandConsole: () => void;
closePanel: () => void;
}
export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
expandConsole,
closePanel,
}: SettingsPaneProps) => {
export const SettingsPane: FunctionComponent = () => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [isExecuting, setIsExecuting] = useState<boolean>(false);
const [pageOption, setPageOption] = useState<string>(
LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage) === Constants.Queries.unlimitedItemsPerPage
@@ -88,7 +82,7 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
logConsoleInfo(
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`
);
closePanel();
closeSidePanel();
e.preventDefault();
};
@@ -101,7 +95,6 @@ export const SettingsPane: FunctionComponent<SettingsPaneProps> = ({
};
const genericPaneProps: RightPaneFormProps = {
expandConsole,
formError: "",
isExecuting,
submitButtonText: "Apply",

View File

@@ -2,7 +2,6 @@
exports[`Settings Pane should render Default properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}
@@ -149,7 +148,6 @@ exports[`Settings Pane should render Default properly 1`] = `
exports[`Settings Pane should render Gremlin properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -3,6 +3,7 @@ import { useBoolean } from "@fluentui/react-hooks";
import React, { FunctionComponent, KeyboardEvent, useState } from "react";
import { Areas, NormalizedEventKey } from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
@@ -12,20 +13,17 @@ import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { PanelLoadingScreen } from "../PanelLoadingScreen";
interface SetupNoteBooksPanelProps {
explorer: Explorer;
closePanel: () => void;
openNotificationConsole: () => void;
panelTitle: string;
panelDescription: string;
}
export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> = ({
explorer,
closePanel,
openNotificationConsole,
panelTitle,
panelDescription,
}: SetupNoteBooksPanelProps): JSX.Element => {
const title = panelTitle;
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const description = panelDescription;
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
const [errorMessage, setErrorMessage] = useState<string>("");
@@ -51,7 +49,7 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNotebookWorkspace, {
dataExplorerArea: Areas.ContextualPane,
paneTitle: title,
paneTitle: panelTitle,
});
const clear = NotificationConsoleUtils.logConsoleProgress("Creating a new default notebook workspace");
@@ -64,13 +62,13 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
);
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks
closePanel();
closeSidePanel();
TelemetryProcessor.traceSuccess(
Action.CreateNotebookWorkspace,
{
dataExplorerArea: Areas.ContextualPane,
paneTitle: title,
paneTitle: panelTitle,
},
startKey
);
@@ -81,7 +79,7 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
Action.CreateNotebookWorkspace,
{
dataExplorerArea: Areas.ContextualPane,
paneTitle: title,
paneTitle: panelTitle,
error: errorMessage,
errorStack: getErrorStack(error),
},
@@ -99,12 +97,7 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
return (
<form className="panelFormWrapper">
{errorMessage && (
<PanelInfoErrorComponent
message={errorMessage}
messageType="error"
showErrorDetails={showErrorDetails}
openNotificationConsole={openNotificationConsole}
/>
<PanelInfoErrorComponent message={errorMessage} messageType="error" showErrorDetails={showErrorDetails} />
)}
<div className="panelMainContent">
<div className="pkPadding">

View File

@@ -89,7 +89,6 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
isExecuting: isExecuting,
submitButtonText: submitButtonLabel,
onSubmit: submit,
expandConsole: () => container.expandConsole(),
};
return (
<RightPaneForm {...props}>

View File

@@ -10,14 +10,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -35,7 +28,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
@@ -60,7 +52,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
@@ -97,7 +88,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
successMessage="Created directory "
>
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -1,23 +1,20 @@
import { mount } from "enzyme";
import * as ko from "knockout";
import React from "react";
import Explorer from "../../Explorer";
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
import * as Entities from "../../Tables/Entities";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
import { AddTableEntityPanel } from "./AddTableEntityPanel";
describe("Excute Add Table Entity Pane", () => {
const fakeExplorer = {} as Explorer;
const fakeQueryTablesTab = {} as QueryTablesTab;
const fakeTableEntityListViewModel = {} as TableListViewModal;
const fakeCassandraApiClient = {} as CassandraAPIDataClient;
fakeTableEntityListViewModel.items = ko.observableArray<Entities.ITableEntity>();
fakeTableEntityListViewModel.headers = [];
const props = {
explorer: fakeExplorer,
closePanel: (): void => undefined,
tableDataClient: new TablesAPIDataClient(),
queryTablesTab: fakeQueryTablesTab,
tableEntityListViewModel: fakeTableEntityListViewModel,
cassandraApiClient: fakeCassandraApiClient,

View File

@@ -5,13 +5,13 @@ import * as _ from "underscore";
import AddPropertyIcon from "../../../../images/Add-property.svg";
import RevertBackIcon from "../../../../images/RevertBack.svg";
import { TableEntity } from "../../../Common/TableEntity";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import * as TableConstants from "../../Tables/Constants";
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
import * as Entities from "../../Tables/Entities";
import { CassandraAPIDataClient, CassandraTableKey } from "../../Tables/TableDataClient";
import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../../Tables/TableDataClient";
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
import * as Utilities from "../../Tables/Utilities";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
@@ -37,8 +37,7 @@ import {
} from "./Validators/EntityTableHelper";
interface AddTableEntityPanelProps {
explorer: Explorer;
closePanel: () => void;
tableDataClient: TableDataClient;
queryTablesTab: QueryTablesTab;
tableEntityListViewModel: TableEntityListViewModel;
cassandraApiClient: CassandraAPIDataClient;
@@ -57,12 +56,12 @@ interface EntityRowType {
}
export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> = ({
explorer,
closePanel,
tableDataClient,
queryTablesTab,
tableEntityListViewModel,
cassandraApiClient,
}: AddTableEntityPanelProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [entities, setEntities] = useState<EntityRowType[]>([]);
const [selectedRow, setSelectedRow] = useState<number>(0);
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
@@ -106,15 +105,12 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
event.preventDefault();
const entity: Entities.ITableEntity = entityFromAttributes(entities);
const newEntity: Entities.ITableEntity = await explorer.tableDataClient.createDocument(
queryTablesTab.collection,
entity
);
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
await tableEntityListViewModel.addEntityToCache(newEntity);
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
tableEntityListViewModel.redrawTableThrottled();
}
closePanel();
closeSidePanel();
};
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
@@ -296,7 +292,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
}}
/>
}
closePanel={() => closePanel()}
isConsoleExpanded={false}
/>
);
@@ -308,7 +303,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
panelWidth="700px"
isOpen={true}
panelContent={renderPanelContent()}
closePanel={() => closePanel()}
isConsoleExpanded={false}
/>
);

View File

@@ -1,15 +1,13 @@
import { mount } from "enzyme";
import * as ko from "knockout";
import React from "react";
import Explorer from "../../Explorer";
import TableListViewModal from "../../Tables/DataTable/TableEntityListViewModel";
import * as Entities from "../../Tables/Entities";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
import { CassandraAPIDataClient, TablesAPIDataClient } from "../../Tables/TableDataClient";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
import { EditTableEntityPanel } from "./EditTableEntityPanel";
describe("Excute Edit Table Entity Pane", () => {
const fakeExplorer = {} as Explorer;
const fakeQueryTablesTab = {} as QueryTablesTab;
const fakeTableEntityListViewModel = {} as TableListViewModal;
fakeTableEntityListViewModel.items = ko.observableArray<Entities.ITableEntity>();
@@ -18,8 +16,7 @@ describe("Excute Edit Table Entity Pane", () => {
fakeTableEntityListViewModel.selected = ko.observableArray<Entities.ITableEntity>([{}]);
const props = {
explorer: fakeExplorer,
closePanel: (): void => undefined,
tableDataClient: new TablesAPIDataClient(),
queryTablesTab: fakeQueryTablesTab,
tableEntityListViewModel: fakeTableEntityListViewModel,
cassandraApiClient: fakeCassandraApiClient,

View File

@@ -5,13 +5,13 @@ import * as _ from "underscore";
import AddPropertyIcon from "../../../../images/Add-property.svg";
import RevertBackIcon from "../../../../images/RevertBack.svg";
import { TableEntity } from "../../../Common/TableEntity";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import * as TableConstants from "../../Tables/Constants";
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel";
import * as Entities from "../../Tables/Entities";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
import { CassandraAPIDataClient, TableDataClient } from "../../Tables/TableDataClient";
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
import { PanelContainerComponent } from "../PanelContainerComponent";
@@ -34,8 +34,7 @@ import {
} from "./Validators/EntityTableHelper";
interface EditTableEntityPanelProps {
explorer: Explorer;
closePanel: () => void;
tableDataClient: TableDataClient;
queryTablesTab: QueryTablesTab;
tableEntityListViewModel: TableEntityListViewModel;
cassandraApiClient: CassandraAPIDataClient;
@@ -55,12 +54,12 @@ interface EntityRowType {
}
export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps> = ({
explorer,
closePanel,
tableDataClient,
queryTablesTab,
tableEntityListViewModel,
cassandraApiClient,
}: EditTableEntityPanelProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [entities, setEntities] = useState<EntityRowType[]>([]);
const [selectedRow, setSelectedRow] = useState<number>(0);
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
@@ -197,9 +196,9 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
}
event.preventDefault();
const entity: Entities.ITableEntity = entityFromAttributes(entities);
const tableDataClient = userContext.apiType === "Cassandra" ? cassandraApiClient : explorer.tableDataClient;
const newTableDataClient = userContext.apiType === "Cassandra" ? cassandraApiClient : tableDataClient;
const originalDocumentData = userContext.apiType === "Cassandra" ? originalDocument[0] : originalDocument;
const newEntity: Entities.ITableEntity = await tableDataClient.updateDocument(
const newEntity: Entities.ITableEntity = await newTableDataClient.updateDocument(
queryTablesTab.collection,
originalDocumentData,
entity
@@ -210,7 +209,7 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
}
tableEntityListViewModel.selected.removeAll();
tableEntityListViewModel.selected.push(newEntity);
closePanel();
closeSidePanel();
};
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
@@ -391,7 +390,6 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
}}
/>
}
closePanel={() => closePanel()}
isConsoleExpanded={false}
/>
);
@@ -403,7 +401,6 @@ export const EditTableEntityPanel: FunctionComponent<EditTableEntityPanelProps>
panelWidth="700px"
isOpen={true}
panelContent={renderPanelContent()}
closePanel={() => closePanel()}
isConsoleExpanded={false}
/>
);

View File

@@ -1,14 +1,12 @@
import { Checkbox, Text } from "@fluentui/react";
import React, { FunctionComponent, useEffect, useState } from "react";
import { useSidePanel } from "../../../../hooks/useSidePanel";
import { userContext } from "../../../../UserContext";
import Explorer from "../../../Explorer";
import * as Constants from "../../../Tables/Constants";
import QueryViewModel from "../../../Tables/QueryBuilder/QueryViewModel";
import { RightPaneForm, RightPaneFormProps } from "../../RightPaneForm/RightPaneForm";
interface TableQuerySelectPanelProps {
explorer: Explorer;
closePanel: () => void;
queryViewModel: QueryViewModel;
}
@@ -19,10 +17,10 @@ interface ISelectColumn {
}
export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps> = ({
explorer,
closePanel,
queryViewModel,
}: TableQuerySelectPanelProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [columnOptions, setColumnOptions] = useState<ISelectColumn[]>([
{ columnName: "", selected: true, editable: false },
]);
@@ -31,7 +29,7 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
const onSubmit = (): void => {
queryViewModel.selectText(getParameters());
queryViewModel.getSelectMessage();
closePanel();
closeSidePanel();
};
const props: RightPaneFormProps = {
@@ -39,7 +37,6 @@ export const TableQuerySelectPanel: FunctionComponent<TableQuerySelectPanelProps
isExecuting: false,
submitButtonText: "OK",
onSubmit,
expandConsole: () => explorer.expandConsole(),
};
const handleClick = (isChecked: boolean, selectedColumn: string): void => {

View File

@@ -11,7 +11,6 @@ exports[`Table query select Panel should render Default properly 1`] = `
}
>
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -3,9 +3,8 @@
exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
<AddTableEntityPanel
cassandraApiClient={Object {}}
closePanel={[Function]}
explorer={Object {}}
queryTablesTab={Object {}}
tableDataClient={TablesAPIDataClient {}}
tableEntityListViewModel={
Object {
"headers": Array [],
@@ -14,7 +13,6 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
}
>
<PanelContainerComponent
closePanel={[Function]}
headerText="Add Table Row"
isConsoleExpanded={false}
isOpen={true}

View File

@@ -3,9 +3,8 @@
exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
<EditTableEntityPanel
cassandraApiClient={Object {}}
closePanel={[Function]}
explorer={Object {}}
queryTablesTab={Object {}}
tableDataClient={TablesAPIDataClient {}}
tableEntityListViewModel={
Object {
"headers": Array [],
@@ -15,7 +14,6 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
}
>
<PanelContainerComponent
closePanel={[Function]}
headerText="Edit Table Entity"
isConsoleExpanded={false}
isOpen={true}

View File

@@ -1,20 +1,16 @@
import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { Upload } from "../../../Common/Upload/Upload";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface UploadFilePanelProps {
expandConsole: () => void;
closePanel: () => void;
uploadFile: (name: string, content: string) => Promise<NotebookContentItem>;
}
export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
expandConsole,
closePanel,
uploadFile,
}: UploadFilePanelProps) => {
export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({ uploadFile }: UploadFilePanelProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const extensions: string = undefined; //ex. ".ipynb"
const errorMessage = "Could not upload file";
const inProgressMessage = "Uploading file to notebook server";
@@ -42,7 +38,7 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
.then(
() => {
logConsoleInfo(`${successMessage} ${file.name}`);
closePanel();
closeSidePanel();
},
(error: string) => {
setFormErrors(errorMessage);
@@ -79,7 +75,6 @@ export const UploadFilePane: FunctionComponent<UploadFilePanelProps> = ({
};
const props: RightPaneFormProps = {
expandConsole,
formError: formErrors,
isExecuting: isExecuting,
submitButtonText: "Upload",

View File

@@ -4,7 +4,6 @@ import Explorer from "../../Explorer";
import { UploadItemsPane } from "./UploadItemsPane";
const props = {
explorer: new Explorer(),
closePanel: (): void => undefined,
};
describe("Upload Items Pane", () => {
it("should render Default properly", () => {

View File

@@ -26,7 +26,6 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explo
}
const selectedCollection = explorer.findSelectedCollection();
setIsExecuting(true);
selectedCollection
@@ -51,7 +50,6 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ explo
};
const props: RightPaneFormProps = {
expandConsole: () => explorer.expandConsole(),
formError,
isExecuting: isExecuting,
submitButtonText: "Upload",

View File

@@ -2,7 +2,6 @@
exports[`Upload Items Pane should render Default properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
onSubmit={[Function]}
submitButtonText="Upload"

View File

@@ -8,14 +8,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"canSaveQueries": [Function],
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
"commandBarComponentAdapter": CommandBarComponentAdapter {
"container": [Circular],
"isNotebookTabActive": [Function],
"parameters": [Function],
"tabsButtons": Array [],
},
"databases": [Function],
"isAccountReady": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
@@ -36,7 +29,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"notebookServerInfo": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"openSidePanel": undefined,
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
@@ -62,7 +54,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"selectedDatabaseId": [Function],
"selectedNode": [Function],
"setInProgressConsoleDataIdToBeDeleted": undefined,
"setIsNotificationConsoleExpanded": undefined,
"setNotificationConsoleData": undefined,
"sparkClusterConnectionInfo": [Function],
"splitter": Splitter {
@@ -92,7 +83,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
}
>
<RightPaneForm
expandConsole={[Function]}
formError=""
isExecuting={false}
onSubmit={[Function]}

View File

@@ -2,7 +2,6 @@ import * as ko from "knockout";
import { DatabaseAccount } from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { updateUserContext } from "../../UserContext";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer";
import DocumentId from "../Tree/DocumentId";
import DocumentsTab from "./DocumentsTab";
@@ -17,7 +16,6 @@ describe("Documents tab", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.buildQuery("")).toContain("select");
@@ -93,7 +91,6 @@ describe("Documents tab", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(false);
@@ -108,7 +105,6 @@ describe("Documents tab", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(false);
@@ -123,7 +119,6 @@ describe("Documents tab", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(true);
@@ -141,7 +136,6 @@ describe("Documents tab", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(false);
@@ -156,7 +150,6 @@ describe("Documents tab", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
expect(documentsTab.showPartitionKey).toBe(true);

View File

@@ -4,6 +4,7 @@ import NewVertexIcon from "../../../images/NewVertex.svg";
import StyleIcon from "../../../images/Style.svg";
import { DatabaseAccount } from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { useSidePanel } from "../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import {
GraphAccessor,
@@ -11,9 +12,6 @@ import {
GraphExplorerError,
GraphExplorerProps,
} from "../Graph/GraphExplorerComponent/GraphExplorer";
// import { GraphAccessor, GraphExplorer, GraphExplorerError } from "../Graph/GraphExplorerComponent/GraphExplorer";
// import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplorerAdapter";
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
import { GraphStylingPanel } from "../Panes/GraphStylingPanel/GraphStylingPanel";
import { NewVertexPanel } from "../Panes/NewVertexPanel/NewVertexPanel";
import TabsBase from "./TabsBase";
@@ -71,7 +69,6 @@ export default class GraphTab extends TabsBase {
private isFilterQueryLoading: ko.Observable<boolean>;
private isValidQuery: ko.Observable<boolean>;
private collectionPartitionKeyProperty: string;
private contextualPane: ContextualPaneBase;
public graphExplorer: GraphExplorer;
public options: GraphTabOptions;
constructor(options: GraphTabOptions) {
@@ -159,12 +156,10 @@ export default class GraphTab extends TabsBase {
/* Command bar */
private showNewVertexEditor(): void {
this.collection.container.openSidePanel(
useSidePanel.getState().openSidePanel(
"New Vertex",
<NewVertexPanel
explorer={this.collection.container}
partitionKeyPropertyProp={this.collectionPartitionKeyProperty}
openNotificationConsole={() => this.collection.container.expandConsole()}
onSubmit={(
result: ViewModels.NewVertexData,
onError: (errorMessage: string) => void,
@@ -173,7 +168,7 @@ export default class GraphTab extends TabsBase {
this.graphAccessor.addVertex(result).then(
() => {
onSuccess();
this.contextualPane.cancel();
useSidePanel.getState().closeSidePanel();
},
(error: GraphExplorerError) => {
onError(error.title);
@@ -184,10 +179,9 @@ export default class GraphTab extends TabsBase {
);
}
public openStyling(): void {
this.collection.container.openSidePanel(
useSidePanel.getState().openSidePanel(
"Graph Style",
<GraphStylingPanel
closePanel={this.collection.container.closeSidePanel}
igraphConfigUiData={this.igraphConfigUiData}
igraphConfig={this.igraphConfig}
getValues={(igraphConfig?: IGraphConfig): void => {

View File

@@ -2,7 +2,6 @@ import * as ko from "knockout";
import { DatabaseAccount } from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { updateUserContext } from "../../UserContext";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer";
import QueryTab from "./QueryTab";
@@ -26,7 +25,6 @@ describe("Query Tab", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {},
});
}

View File

@@ -10,6 +10,7 @@ import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
@@ -198,7 +199,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
}
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
this.collection && this.collection.container.expandConsole();
useNotificationConsole.getState().expandConsole();
return false;
};

View File

@@ -9,6 +9,7 @@ import { updateStoredProcedure } from "../../Common/dataAccess/updateStoredProce
import editable from "../../Common/EditableUtility";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
@@ -136,7 +137,7 @@ export default class StoredProcedureTab extends ScriptTabBase {
}
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
this.collection && this.collection.container.expandConsole();
useNotificationConsole.getState().expandConsole();
return false;
};

View File

@@ -3,11 +3,13 @@ import * as Constants from "../../Common/Constants";
import * as ThemeUtility from "../../Common/ThemeUtility";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { RouteHandler } from "../../RouteHandlers/RouteHandler";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import { TabsManager } from "./TabsManager";
@@ -122,8 +124,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
}
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
this.collection?.container?.expandConsole();
this.database?.container?.expandConsole();
useNotificationConsole.getState().expandConsole();
useNotificationConsole.getState().expandConsole();
return false;
};
@@ -162,7 +164,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
protected updateNavbarWithTabsButtons = (): void => {
if (this.isActive()) {
this.getContainer().onUpdateTabsButtons(this.getTabsButtons());
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
};
}

View File

@@ -52,7 +52,6 @@ describe("Tabs manager tests", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: undefined,
});
documentsTab = new DocumentsTab({
@@ -63,7 +62,6 @@ describe("Tabs manager tests", () => {
title: "",
tabPath: "",
hashLocation: "",
onUpdateTabsButtons: undefined,
});
// make sure tabs have different tabId

View File

@@ -21,6 +21,7 @@ import { userContext } from "../../UserContext";
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
import ConflictsTab from "../Tabs/ConflictsTab";
import DocumentsTab from "../Tabs/DocumentsTab";
@@ -221,7 +222,7 @@ export default class Collection implements ViewModels.Collection {
} else {
this.expandCollection();
}
this.container.onUpdateTabsButtons([]);
useCommandBar.getState().setContextButtons([]);
this.container.tabsManager.refreshActiveTab(
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
);
@@ -299,7 +300,6 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(documentsTab);
@@ -346,7 +346,6 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(conflictsTab);
@@ -401,7 +400,6 @@ export default class Collection implements ViewModels.Collection {
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(queryTablesTab);
@@ -454,7 +452,6 @@ export default class Collection implements ViewModels.Collection {
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(graphTab);
@@ -501,7 +498,6 @@ export default class Collection implements ViewModels.Collection {
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
}
@@ -547,7 +543,6 @@ export default class Collection implements ViewModels.Collection {
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/schemaAnalyzer`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
})
);
};
@@ -587,7 +582,6 @@ export default class Collection implements ViewModels.Collection {
collection: this,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
};
let settingsTabV2 = matchingTabs && (matchingTabs[0] as CollectionSettingsTabV2);
@@ -632,7 +626,6 @@ export default class Collection implements ViewModels.Collection {
queryText: queryText,
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(queryTab);
@@ -660,7 +653,6 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoQuery`,
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(mongoQueryTab);
@@ -692,7 +684,6 @@ export default class Collection implements ViewModels.Collection {
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(graphTab);
@@ -707,7 +698,6 @@ export default class Collection implements ViewModels.Collection {
collection: this,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(mongoShellTab);

View File

@@ -81,7 +81,6 @@ export default class Database implements ViewModels.Database {
database: this,
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
};
settingsTab = new DatabaseSettingsTabV2(tabOptions);
settingsTab.pendingNotification(pendingNotification);

View File

@@ -3,13 +3,12 @@ import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import DocumentId from "./DocumentId";
import DocumentsTab from "../Tabs/DocumentsTab";
import Q from "q";
import QueryTab from "../Tabs/QueryTab";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import DocumentsTab from "../Tabs/DocumentsTab";
import QueryTab from "../Tabs/QueryTab";
import TabsBase from "../Tabs/TabsBase";
import DocumentId from "./DocumentId";
export default class ResourceTokenCollection implements ViewModels.CollectionBase {
public nodeKind: string;
@@ -96,7 +95,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
partitionKey: collection.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(queryTab);
@@ -143,7 +141,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(documentsTab);

View File

@@ -1,12 +1,12 @@
import { shallow } from "enzyme";
import * as ko from "knockout";
import React from "react";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import React from "react";
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
import { shallow } from "enzyme";
import { TreeComponent, TreeNode, TreeComponentProps } from "../Controls/TreeComponent/TreeComponent";
import { TreeComponent, TreeComponentProps, TreeNode } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
import Collection from "./Collection";
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
const schema: DataModels.ISchema = {
id: "fakeSchemaId",
@@ -211,9 +211,6 @@ const schema: DataModels.ISchema = {
const createMockContainer = (): Explorer => {
const mockContainer = new Explorer();
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
mockContainer.onUpdateTabsButtons = () => {
return;
};
return mockContainer;
};

View File

@@ -27,6 +27,7 @@ import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactor
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil";
@@ -211,7 +212,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
}
databaseNode.isLoading = false;
database.selectDatabase();
this.container.onUpdateTabsButtons([]);
useCommandBar.getState().setContextButtons([]);
this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
},
onContextMenuOpen: () => this.container.selectedNode(database),
@@ -330,7 +331,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
onClick: () => {
// Rewritten version of expandCollapseCollection
this.container.selectedNode(collection);
this.container.onUpdateTabsButtons([]);
useCommandBar.getState().setContextButtons([]);
this.container.tabsManager.refreshActiveTab(
(tab: TabsBase) =>
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId

View File

@@ -1,19 +1,17 @@
import { shallow } from "enzyme";
import * as ko from "knockout";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
import React from "react";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import React from "react";
import { TreeComponent, TreeComponentProps, TreeNode } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
import ResourceTokenCollection from "./ResourceTokenCollection";
import { ResourceTreeAdapterForResourceToken } from "./ResourceTreeAdapterForResourceToken";
import { shallow } from "enzyme";
import { TreeComponent, TreeNode, TreeComponentProps } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
const createMockContainer = (): Explorer => {
let mockContainer = {} as Explorer;
mockContainer.resourceTokenCollection = createMockCollection(mockContainer);
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
mockContainer.onUpdateTabsButtons = () => {};
return mockContainer;
};

View File

@@ -1,13 +1,14 @@
import * as ko from "knockout";
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import * as React from "react";
import * as ViewModels from "../../Contracts/ViewModels";
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
import CollectionIcon from "../../../images/tree-collection.svg";
import Explorer from "../Explorer";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext";
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
public parameters: ko.Observable<number>;
@@ -59,7 +60,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
onClick: () => {
// Rewritten version of expandCollapseCollection
this.container.selectedNode(collection);
this.container.onUpdateTabsButtons([]);
useCommandBar.getState().setContextButtons([]);
this.container.tabsManager.refreshActiveTab(
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
);

View File

@@ -76,7 +76,6 @@ export default class StoredProcedure {
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
});
source.container.tabsManager.activateNewTab(storedProcedureTab);
@@ -122,7 +121,6 @@ export default class StoredProcedure {
this.collection.databaseId,
this.collection.id()
)}/sprocs/${this.id()}`,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(storedProcedureTab);

View File

@@ -58,7 +58,6 @@ export default class Trigger {
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
});
source.container.tabsManager.activateNewTab(triggerTab);
@@ -97,7 +96,6 @@ export default class Trigger {
this.collection.databaseId,
this.collection.id()
)}/triggers/${this.id()}`,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(triggerTab);

View File

@@ -44,7 +44,6 @@ export default class UserDefinedFunction {
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
});
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
@@ -81,7 +80,6 @@ export default class UserDefinedFunction {
this.collection.databaseId,
this.collection.id()
)}/udfs/${this.id()}`,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
});
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);

View File

@@ -1,5 +1,5 @@
import { useBoolean } from "@fluentui/react-hooks";
import { initializeIcons } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react";
import { render } from "react-dom";
import ChevronRight from "../images/chevron-right.svg";
@@ -8,6 +8,7 @@ import { AuthType } from "./AuthType";
import { DatabaseAccount } from "./Contracts/DataModels";
import "./Explorer/Menus/NavBar/MeControlComponent.less";
import { useAADAuth } from "./hooks/useAADAuth";
import { useConfig } from "./hooks/useConfig";
import { useTokenMetadata } from "./hooks/usePortalAccessToken";
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
import { AccountSwitcher } from "./Platform/Hosted/Components/AccountSwitcher";
@@ -30,8 +31,8 @@ const App: React.FunctionComponent = () => {
// For showing/hiding panel
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
const { isLoggedIn, armToken, graphToken, aadToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
const config = useConfig();
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
const [connectionString, setConnectionString] = React.useState<string>();
@@ -50,7 +51,6 @@ const App: React.FunctionComponent = () => {
authType: AuthType.AAD,
databaseAccount,
authorizationToken: armToken,
aadToken,
};
} else if (authType === AuthType.EncryptedToken) {
frameWindow.hostedConfig = {
@@ -75,7 +75,7 @@ const App: React.FunctionComponent = () => {
});
const showExplorer =
(isLoggedIn && databaseAccount) ||
(config && isLoggedIn && databaseAccount) ||
(encryptedTokenMetadata && encryptedTokenMetadata) ||
(authType === AuthType.ResourceToken && connectionString);

View File

@@ -7,7 +7,6 @@ export interface HostedExplorerChildFrame extends Window {
}
export interface AAD {
aadToken: string;
authType: AuthType.AAD;
databaseAccount: DatabaseAccount;
authorizationToken: string;

View File

@@ -37,18 +37,18 @@ import "./Explorer/Controls/TreeComponent/treeComponent.less";
import { ExplorerParams } from "./Explorer/Explorer";
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
import { NotificationConsoleComponent } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { NotificationConsole } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import "./Explorer/Panes/PanelComponent.less";
import { PanelContainerComponent } from "./Explorer/Panes/PanelContainerComponent";
import { SidePanel } from "./Explorer/Panes/PanelContainerComponent";
import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen";
import "./Explorer/SplashScreen/SplashScreen.less";
import "./Explorer/Tabs/QueryTab.less";
import { Tabs } from "./Explorer/Tabs/Tabs";
import { useConfig } from "./hooks/useConfig";
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
import { useSidePanel } from "./hooks/useSidePanel";
import { useTabs } from "./hooks/useTabs";
import "./Libs/jquery";
import "./Shared/appInsights";
@@ -56,21 +56,16 @@ import "./Shared/appInsights";
initializeIcons();
const App: React.FunctionComponent = () => {
const [isNotificationConsoleExpanded, setIsNotificationConsoleExpanded] = useState(false);
const [notificationConsoleData, setNotificationConsoleData] = useState(undefined);
//TODO: Refactor so we don't need to pass the id to remove a console data
const [inProgressConsoleDataIdToBeDeleted, setInProgressConsoleDataIdToBeDeleted] = useState("");
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
const { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel } = useSidePanel();
const { tabs, activeTab, tabsManager } = useTabs();
const explorerParams: ExplorerParams = {
setIsNotificationConsoleExpanded,
setNotificationConsoleData,
setInProgressConsoleDataIdToBeDeleted,
openSidePanel,
closeSidePanel,
tabsManager,
};
@@ -94,7 +89,7 @@ const App: React.FunctionComponent = () => {
<div className="flexContainer">
<div id="divExplorer" className="flexContainer hideOverflows">
{/* Main Command Bar - Start */}
<div data-bind="react: commandBarComponentAdapter" />
<CommandBar container={explorer} />
{/* Collections Tree and Tabs - Begin */}
<div className="resourceTreeAndTabs">
{/* Collections Tree - Start */}
@@ -125,21 +120,13 @@ const App: React.FunctionComponent = () => {
aria-label="Notification console"
id="explorerNotificationConsole"
>
<NotificationConsoleComponent
isConsoleExpanded={isNotificationConsoleExpanded}
<NotificationConsole
consoleData={notificationConsoleData}
inProgressConsoleDataIdToBeDeleted={inProgressConsoleDataIdToBeDeleted}
setIsConsoleExpanded={setIsNotificationConsoleExpanded}
/>
</div>
</div>
<PanelContainerComponent
isOpen={isPanelOpen}
panelContent={panelContent}
headerText={headerText}
closePanel={closeSidePanel}
isConsoleExpanded={isNotificationConsoleExpanded}
/>
<SidePanel />
<Dialog />
</div>
);

View File

@@ -1,3 +1,4 @@
import * as msal from "@azure/msal-browser";
import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants";
import * as Logger from "../Common/Logger";
@@ -40,3 +41,21 @@ export function decryptJWTToken(token: string) {
return JSON.parse(tokenPayload);
}
export function getMsalInstance() {
const config: msal.Configuration = {
cache: {
cacheLocation: "localStorage",
},
auth: {
authority: "https://login.microsoftonline.com/common",
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
},
};
if (process.env.NODE_ENV === "development") {
config.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net";
}
const msalInstance = new msal.PublicClientApplication(config);
return msalInstance;
}

View File

@@ -1,22 +1,10 @@
import * as msal from "@azure/msal-browser";
import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react";
import { configContext } from "../ConfigContext";
import { getMsalInstance } from "../Utils/AuthorizationUtils";
const config: msal.Configuration = {
cache: {
cacheLocation: "localStorage",
},
auth: {
authority: "https://login.microsoftonline.com/common",
clientId: "203f1145-856a-4232-83d4-a43568fba23d",
},
};
if (process.env.NODE_ENV === "development") {
config.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net";
}
const msalInstance = new msal.PublicClientApplication(config);
const msalInstance = getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId");
@@ -25,7 +13,6 @@ interface ReturnType {
isLoggedIn: boolean;
graphToken: string;
armToken: string;
aadToken: string;
login: () => void;
logout: () => void;
tenantId: string;
@@ -41,11 +28,13 @@ export function useAADAuth(): ReturnType {
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
const [graphToken, setGraphToken] = React.useState<string>();
const [armToken, setArmToken] = React.useState<string>();
const [aadToken, setAadToken] = React.useState<string>();
msalInstance.setActiveAccount(account);
const login = React.useCallback(async () => {
const response = await msalInstance.loginPopup();
const response = await msalInstance.loginPopup({
redirectUri: configContext.msalRedirectURI,
scopes: [],
});
setLoggedIn();
setAccount(response.account);
setTenantId(response.tenantId);
@@ -61,6 +50,7 @@ export function useAADAuth(): ReturnType {
const switchTenant = React.useCallback(
async (id) => {
const response = await msalInstance.loginPopup({
redirectUri: configContext.msalRedirectURI,
authority: `https://login.microsoftonline.com/${id}`,
scopes: [],
});
@@ -81,13 +71,9 @@ export function useAADAuth(): ReturnType {
authority: `https://login.microsoftonline.com/${tenantId}`,
scopes: ["https://management.azure.com//.default"],
}),
msalInstance.acquireTokenSilent({
scopes: ["https://cosmos.azure.com/.default"],
}),
]).then(([graphTokenResponse, armTokenResponse, aadTokenResponse]) => {
]).then(([graphTokenResponse, armTokenResponse]) => {
setGraphToken(graphTokenResponse.accessToken);
setArmToken(armTokenResponse.accessToken);
setAadToken(aadTokenResponse.accessToken);
});
}
}, [account, tenantId]);
@@ -98,7 +84,6 @@ export function useAADAuth(): ReturnType {
isLoggedIn,
graphToken,
armToken,
aadToken,
login,
logout,
switchTenant,

View File

@@ -28,11 +28,13 @@ import { CollectionCreation } from "../Shared/Constants";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { PortalEnv, updateUserContext, userContext } from "../UserContext";
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
import { getMsalInstance } from "../Utils/AuthorizationUtils";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
// This hook will create a new instance of Explorer.ts and bind it to the DOM
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
// Pleas tread carefully :)
// Please tread carefully :)
export function useKnockoutExplorer(platform: Platform, explorerParams: ExplorerParams): Explorer {
const [explorer, setExplorer] = useState<Explorer>();
@@ -83,16 +85,37 @@ async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParam
updateUserContext({
authType: AuthType.AAD,
authorizationToken: `Bearer ${config.authorizationToken}`,
aadToken: config.aadToken,
});
const account = config.databaseAccount;
const accountResourceId = account.id;
const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
const keys = await listKeys(subscriptionId, resourceGroup, account.name);
let aadToken;
let keys: DatabaseAccountListKeysResult = {};
if (account.properties?.documentEndpoint) {
const hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default");
const msalInstance = getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
msalInstance.setActiveAccount(cachedAccount);
const aadTokenResponse = await msalInstance.acquireTokenSilent({
forceRefresh: true,
scopes: [hrefEndpoint],
});
aadToken = aadTokenResponse.accessToken;
}
try {
keys = await listKeys(subscriptionId, resourceGroup, account.name);
} catch (e) {
if (userContext.features.enableAadDataPlane) {
console.warn(e);
} else {
throw new Error(`List keys failed: ${e.message}`);
}
}
updateUserContext({
subscriptionId,
resourceGroup,
aadToken,
databaseAccount: config.databaseAccount,
masterKey: keys.primaryMasterKey,
});

View File

@@ -0,0 +1,14 @@
import create, { UseStore } from "zustand";
export interface NotificationConsoleState {
isExpanded: boolean;
expandConsole: () => void;
// TODO Remove this method. Add a `closeConsole` method instead
setIsExpanded: (isExpanded: boolean) => void;
}
export const useNotificationConsole: UseStore<NotificationConsoleState> = create((set) => ({
isExpanded: false,
expandConsole: () => set((state) => ({ ...state, isExpanded: true })),
setIsExpanded: (isExpanded) => set((state) => ({ ...state, isExpanded })),
}));

View File

@@ -1,35 +1,15 @@
import { useState } from "react";
import create, { UseStore } from "zustand";
export interface SidePanelHooks {
isPanelOpen: boolean;
panelContent: JSX.Element;
headerText: string;
export interface SidePanelState {
isOpen: boolean;
panelContent?: JSX.Element;
headerText?: string;
openSidePanel: (headerText: string, panelContent: JSX.Element, onClose?: () => void) => void;
closeSidePanel: () => void;
}
export const useSidePanel = (): SidePanelHooks => {
const [isPanelOpen, setIsPanelOpen] = useState<boolean>(false);
const [panelContent, setPanelContent] = useState<JSX.Element>();
const [headerText, setHeaderText] = useState<string>();
const [onCloseCallback, setOnCloseCallback] = useState<{ callback: () => void }>();
const openSidePanel = (headerText: string, panelContent: JSX.Element, onClose?: () => void): void => {
setHeaderText(headerText);
setPanelContent(panelContent);
setIsPanelOpen(true);
!!onClose && setOnCloseCallback({ callback: onClose });
};
const closeSidePanel = (): void => {
setHeaderText("");
setPanelContent(undefined);
setIsPanelOpen(false);
if (onCloseCallback) {
onCloseCallback.callback();
setOnCloseCallback(undefined);
}
};
return { isPanelOpen, panelContent, headerText, openSidePanel, closeSidePanel };
};
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({
isOpen: false,
openSidePanel: (headerText, panelContent) => set((state) => ({ ...state, headerText, panelContent, isOpen: true })),
closeSidePanel: () => set((state) => ({ ...state, isOpen: false })),
}));