mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
Compare commits
1 Commits
eslint/fix
...
fixed-tsst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a99e275f71 |
@@ -137,10 +137,18 @@ src/Terminal/NotebookAppContracts.d.ts
|
|||||||
src/applyExplorerBindings.ts
|
src/applyExplorerBindings.ts
|
||||||
src/global.d.ts
|
src/global.d.ts
|
||||||
src/setupTests.ts
|
src/setupTests.ts
|
||||||
|
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
|
||||||
|
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
|
||||||
|
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
|
||||||
|
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
|
||||||
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
|
||||||
|
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
|
||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
||||||
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
||||||
|
|||||||
@@ -230,4 +230,4 @@
|
|||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 120
|
"printWidth": 120
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ export class Flights {
|
|||||||
public static readonly AutoscaleTest = "autoscaletest";
|
public static readonly AutoscaleTest = "autoscaletest";
|
||||||
public static readonly PartitionKeyTest = "partitionkeytest";
|
public static readonly PartitionKeyTest = "partitionkeytest";
|
||||||
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
|
||||||
public static readonly Phoenix = "phoenix";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -338,13 +337,6 @@ export enum ConflictOperationType {
|
|||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConnectionStatusType {
|
|
||||||
Connecting = "Connecting",
|
|
||||||
Allocating = "Allocating",
|
|
||||||
Connected = "Connected",
|
|
||||||
Failed = "Connection Failed",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { ConnectionStatusType } from "../Common/Constants";
|
|
||||||
|
|
||||||
export interface DatabaseAccount {
|
export interface DatabaseAccount {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -498,8 +496,3 @@ export interface MemoryUsageInfo {
|
|||||||
freeKB: number;
|
freeKB: number;
|
||||||
totalKB: number;
|
totalKB: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContainerConnectionInfo {
|
|
||||||
status: ConnectionStatusType;
|
|
||||||
//need to add ram and rom info
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -181,7 +181,8 @@ export const Dialog: FC = () => {
|
|||||||
text: secondaryButtonText,
|
text: secondaryButtonText,
|
||||||
onClick: onSecondaryButtonClick,
|
onClick: onSecondaryButtonClick,
|
||||||
}
|
}
|
||||||
: undefined;
|
: {};
|
||||||
|
|
||||||
return visible ? (
|
return visible ? (
|
||||||
<FluentDialog {...dialogProps}>
|
<FluentDialog {...dialogProps}>
|
||||||
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export interface InputTypeaheadComponentProps {
|
|||||||
* Override default jquery-typeahead options
|
* Override default jquery-typeahead options
|
||||||
* WARNING: do not override input, source or callback to avoid breaking the components behavior.
|
* WARNING: do not override input, source or callback to avoid breaking the components behavior.
|
||||||
*/
|
*/
|
||||||
typeaheadOverrideOptions?: { dynamic: boolean };
|
typeaheadOverrideOptions?: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function gets called when pressing ENTER on the input box
|
* This function gets called when pressing ENTER on the input box
|
||||||
@@ -132,7 +132,6 @@ export class InputTypeaheadComponent extends React.Component<
|
|||||||
|
|
||||||
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
|
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
|
||||||
return choices.filter((choice) =>
|
return choices.filter((choice) =>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
|
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -264,6 +264,6 @@ export class NotebookViewerComponent
|
|||||||
};
|
};
|
||||||
|
|
||||||
private reportAbuse = (): void => {
|
private reportAbuse = (): void => {
|
||||||
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => ({}));
|
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"phoenixClient": PhoenixClient {},
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -102,7 +101,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"phoenixClient": PhoenixClient {},
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import _ from "underscore";
|
|||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import { ConnectionStatusType } from "../Common/Constants";
|
|
||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "../Common/DatabaseAccountUtility";
|
||||||
@@ -17,7 +16,6 @@ import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
|||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
import { IGalleryItem } from "../Juno/JunoClient";
|
import { IGalleryItem } from "../Juno/JunoClient";
|
||||||
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
|
||||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -89,13 +87,12 @@ export default class Explorer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
private phoenixClient: PhoenixClient;
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
this.phoenixClient = new PhoenixClient();
|
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
() => this.refreshCommandBarButtons(),
|
() => this.refreshCommandBarButtons(),
|
||||||
(state) => state.isNotebooksEnabledForAccount
|
(state) => state.isNotebooksEnabledForAccount
|
||||||
@@ -346,43 +343,19 @@ export default class Explorer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
if (userContext.features.phoenix) {
|
|
||||||
const connectionStatus: DataModels.ContainerConnectionInfo = {
|
|
||||||
status: ConnectionStatusType.Allocating,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
const provisionData = {
|
|
||||||
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
|
|
||||||
resourceId: userContext.databaseAccount.id,
|
|
||||||
dbAccountName: userContext.databaseAccount.name,
|
|
||||||
aadToken: userContext.authorizationToken,
|
|
||||||
resourceGroup: userContext.resourceGroup,
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
};
|
|
||||||
const connectionInfo = await this.phoenixClient.containerConnectionInfo(provisionData);
|
|
||||||
if (connectionInfo.data && connectionInfo.data.notebookServerUrl) {
|
|
||||||
connectionStatus.status = ConnectionStatusType.Connected;
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
await this.ensureNotebookWorkspaceRunning();
|
||||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.data.notebookServerUrl,
|
const connectionInfo = await listConnectionInfo(
|
||||||
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
|
userContext.subscriptionId,
|
||||||
});
|
userContext.resourceGroup,
|
||||||
}
|
databaseAccount.name,
|
||||||
} else {
|
"default"
|
||||||
await this.ensureNotebookWorkspaceRunning();
|
);
|
||||||
const connectionInfo = await listConnectionInfo(
|
|
||||||
userContext.subscriptionId,
|
|
||||||
userContext.resourceGroup,
|
|
||||||
databaseAccount.name,
|
|
||||||
"default"
|
|
||||||
);
|
|
||||||
|
|
||||||
useNotebook.getState().setNotebookServerInfo({
|
useNotebook.getState().setNotebookServerInfo({
|
||||||
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||||
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
useNotebook.getState().initializeNotebooksTree(this.notebookManager);
|
||||||
|
|
||||||
@@ -391,7 +364,7 @@ export default class Explorer {
|
|||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetNotebookWorkspace(): void {
|
public resetNotebookWorkspace() {
|
||||||
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
|
||||||
handleError(
|
handleError(
|
||||||
"Attempt to reset notebook workspace, but notebook is not enabled",
|
"Attempt to reset notebook workspace, but notebook is not enabled",
|
||||||
@@ -416,6 +389,7 @@ export default class Explorer {
|
|||||||
if (!databaseAccount) {
|
if (!databaseAccount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { value: workspaces } = await listByDatabaseAccount(
|
const { value: workspaces } = await listByDatabaseAccount(
|
||||||
userContext.subscriptionId,
|
userContext.subscriptionId,
|
||||||
@@ -932,7 +906,7 @@ export default class Explorer {
|
|||||||
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
await this.notebookManager?.notebookContentClient.updateItemChildrenInPlace(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openNotebookTerminal(kind: ViewModels.TerminalKind): void {
|
public openNotebookTerminal(kind: ViewModels.TerminalKind) {
|
||||||
let title: string;
|
let title: string;
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
@@ -1052,10 +1026,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async handleOpenFileAction(path: string): Promise<void> {
|
public async handleOpenFileAction(path: string): Promise<void> {
|
||||||
if (
|
if (!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))) {
|
||||||
userContext.features.phoenix === false &&
|
|
||||||
!(await this._containsDefaultNotebookWorkspace(userContext.databaseAccount))
|
|
||||||
) {
|
|
||||||
this._openSetupNotebooksPaneForQuickstart();
|
this._openSetupNotebooksPaneForQuickstart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1101,13 +1072,10 @@ export default class Explorer {
|
|||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases();
|
: this.refreshAllDatabases();
|
||||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||||
let isNotebookEnabled = true;
|
const isNotebookEnabled: boolean =
|
||||||
if (!userContext.features.phoenix) {
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
isNotebookEnabled =
|
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
||||||
userContext.authType !== AuthType.ResourceToken &&
|
userContext.features.enableNotebooks);
|
||||||
((await this._containsDefaultNotebookWorkspace(userContext.databaseAccount)) ||
|
|
||||||
userContext.features.enableNotebooks);
|
|
||||||
}
|
|
||||||
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
|
||||||
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
useNotebook.getState().setIsShellEnabled(isNotebookEnabled && isPublicInternetAccessAllowed());
|
||||||
|
|
||||||
|
|||||||
@@ -330,10 +330,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
const partitionKeyProperty = this.props.collectionPartitionKeyProperty;
|
||||||
|
|
||||||
// aggregate all the properties, remove dropped ones
|
// aggregate all the properties, remove dropped ones
|
||||||
const finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
let finalProperties = editedProperties.existingProperties.concat(editedProperties.addedProperties);
|
||||||
|
|
||||||
// Compose the query
|
// Compose the query
|
||||||
const pkId = editedProperties.pkId;
|
let pkId = editedProperties.pkId;
|
||||||
let updateQueryFragment = "";
|
let updateQueryFragment = "";
|
||||||
|
|
||||||
finalProperties.forEach((p) => {
|
finalProperties.forEach((p) => {
|
||||||
@@ -473,7 +473,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pairs: any[] = data;
|
let pairs: any[] = data;
|
||||||
for (let i = 0; i < pairs.length; i++) {
|
for (let i = 0; i < pairs.length; i++) {
|
||||||
const item = pairs[i];
|
const item = pairs[i];
|
||||||
if (
|
if (
|
||||||
@@ -772,8 +772,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const edge = edges[0];
|
let edge = edges[0];
|
||||||
const graphData = this.originalGraphData;
|
let graphData = this.originalGraphData;
|
||||||
graphData.addEdge(edge);
|
graphData.addEdge(edge);
|
||||||
|
|
||||||
// Allow loadNeighbors to load list new edge
|
// Allow loadNeighbors to load list new edge
|
||||||
@@ -803,7 +803,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
public removeEdge(edgeId: string): Q.Promise<any> {
|
public removeEdge(edgeId: string): Q.Promise<any> {
|
||||||
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
return this.submitToBackend(`g.E('${GraphUtil.escapeSingleQuotes(edgeId)}').drop()`).then(
|
||||||
() => {
|
() => {
|
||||||
const graphData = this.originalGraphData;
|
let graphData = this.originalGraphData;
|
||||||
graphData.removeEdge(edgeId, false);
|
graphData.removeEdge(edgeId, false);
|
||||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||||
},
|
},
|
||||||
@@ -826,9 +826,9 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vertices: any[] = data;
|
let vertices: any[] = data;
|
||||||
if (vertices.length > 0) {
|
if (vertices.length > 0) {
|
||||||
const v0 = vertices[0];
|
let v0 = vertices[0];
|
||||||
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
if (!v0.hasOwnProperty("id") || !v0.hasOwnProperty("type") || v0.type !== "vertex") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -933,7 +933,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
throw { title: err };
|
throw { title: err };
|
||||||
}
|
}
|
||||||
|
|
||||||
const vertex = vertices[0];
|
let vertex = vertices[0];
|
||||||
const graphData = this.originalGraphData;
|
const graphData = this.originalGraphData;
|
||||||
graphData.addVertex(vertex);
|
graphData.addVertex(vertex);
|
||||||
this.updateGraphData(graphData, this.state.igraphConfig);
|
this.updateGraphData(graphData, this.state.igraphConfig);
|
||||||
@@ -1082,7 +1082,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
||||||
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
||||||
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
||||||
let errorDataStr = "";
|
let errorDataStr: string = "";
|
||||||
if (errorData && errorData.length > 0) {
|
if (errorData && errorData.length > 0) {
|
||||||
console.error(msg, errorData);
|
console.error(msg, errorData);
|
||||||
errorDataStr = ": " + JSON.stringify(errorData);
|
errorDataStr = ": " + JSON.stringify(errorData);
|
||||||
@@ -1224,7 +1224,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return $.map(
|
return $.map(
|
||||||
this.state.rootMap,
|
this.state.rootMap,
|
||||||
(value: any, index: number): LeftPane.CaptionId => {
|
(value: any, index: number): LeftPane.CaptionId => {
|
||||||
const result = GraphData.GraphData.getNodePropValue(value, key);
|
let result = GraphData.GraphData.getNodePropValue(value, key);
|
||||||
return {
|
return {
|
||||||
caption: result !== undefined ? result : value.id,
|
caption: result !== undefined ? result : value.id,
|
||||||
id: value.id,
|
id: value.id,
|
||||||
@@ -1388,7 +1388,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @return id
|
* @return id
|
||||||
*/
|
*/
|
||||||
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
public static getPkIdFromDocumentId(d: DataModels.DocumentId, collectionPartitionKeyProperty: string): string {
|
||||||
const { id } = d;
|
let { id } = d;
|
||||||
if (typeof id !== "string") {
|
if (typeof id !== "string") {
|
||||||
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
||||||
logConsoleError(error);
|
logConsoleError(error);
|
||||||
@@ -1425,7 +1425,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
|
||||||
return this.executeNonPagedDocDbQuery(q).then(
|
return this.executeNonPagedDocDbQuery(q).then(
|
||||||
(documents: DataModels.DocumentId[]) => {
|
(documents: DataModels.DocumentId[]) => {
|
||||||
const possibleVertices = [] as PossibleVertex[];
|
let possibleVertices = [] as PossibleVertex[];
|
||||||
$.each(documents, (index: number, item: any) => {
|
$.each(documents, (index: number, item: any) => {
|
||||||
if (highlightedNodeId && item.id === highlightedNodeId) {
|
if (highlightedNodeId && item.id === highlightedNodeId) {
|
||||||
// Exclude highlighed node in the list
|
// Exclude highlighed node in the list
|
||||||
@@ -1463,16 +1463,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @return promise when done
|
* @return promise when done
|
||||||
*/
|
*/
|
||||||
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
private editGraphEdges(editedEdges: EditedEdges): Q.Promise<any> {
|
||||||
const promises = [];
|
let promises = [];
|
||||||
// Drop edges
|
// Drop edges
|
||||||
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
for (let i = 0; i < editedEdges.droppedIds.length; i++) {
|
||||||
const id = editedEdges.droppedIds[i];
|
let id = editedEdges.droppedIds[i];
|
||||||
promises.push(this.removeEdge(id));
|
promises.push(this.removeEdge(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add edges
|
// Add edges
|
||||||
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
for (let i = 0; i < editedEdges.addedEdges.length; i++) {
|
||||||
const e = editedEdges.addedEdges[i];
|
let e = editedEdges.addedEdges[i];
|
||||||
promises.push(
|
promises.push(
|
||||||
this.createNewEdge(e).then(() => {
|
this.createNewEdge(e).then(() => {
|
||||||
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
// Reload neighbors in case we linked to a vertex that isn't loaded in the graph
|
||||||
@@ -1533,7 +1533,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
private collectNodeProperties(vertices: GraphData.GremlinVertex[]) {
|
||||||
const props = {} as any; // Hashset
|
const props = {} as any; // Hashset
|
||||||
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
$.each(vertices, (index: number, item: GraphData.GremlinVertex) => {
|
||||||
for (const p in item) {
|
for (var p in item) {
|
||||||
// DocDB: Exclude type because it's always 'vertex'
|
// DocDB: Exclude type because it's always 'vertex'
|
||||||
if (p !== "type" && typeof (item as any)[p] === "string") {
|
if (p !== "type" && typeof (item as any)[p] === "string") {
|
||||||
props[p] = true;
|
props[p] = true;
|
||||||
@@ -1543,7 +1543,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
if (item.hasOwnProperty("properties")) {
|
if (item.hasOwnProperty("properties")) {
|
||||||
// TODO This is DocDB-graph specific
|
// TODO This is DocDB-graph specific
|
||||||
// Assume each property value is [{value:... }]
|
// Assume each property value is [{value:... }]
|
||||||
for (const f in item.properties) {
|
for (var f in item.properties) {
|
||||||
props[f] = true;
|
props[f] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1570,21 +1570,21 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this.originalGraphData.getVertexById(id);
|
let data = this.originalGraphData.getVertexById(id);
|
||||||
|
|
||||||
// A bit of translation to make it easier to display
|
// A bit of translation to make it easier to display
|
||||||
const props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
|
||||||
for (const p in data.properties) {
|
for (let p in data.properties) {
|
||||||
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update neighbors
|
// update neighbors
|
||||||
const sources: NeighborVertexBasicInfo[] = [];
|
let sources: NeighborVertexBasicInfo[] = [];
|
||||||
const targets: NeighborVertexBasicInfo[] = [];
|
let targets: NeighborVertexBasicInfo[] = [];
|
||||||
this.props.onResetDefaultGraphConfigValues();
|
this.props.onResetDefaultGraphConfigValues();
|
||||||
const nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
let nodeCaption = this.state.igraphConfigUiData.nodeCaptionChoice;
|
||||||
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
this.updateSelectedNodeNeighbors(data.id, nodeCaption, sources, targets);
|
||||||
const sData: GraphHighlightedNodeData = {
|
let sData: GraphHighlightedNodeData = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
properties: props,
|
properties: props,
|
||||||
@@ -1611,16 +1611,16 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
targets: NeighborVertexBasicInfo[]
|
targets: NeighborVertexBasicInfo[]
|
||||||
): void {
|
): void {
|
||||||
// update neighbors
|
// update neighbors
|
||||||
const gd = this.originalGraphData;
|
let gd = this.originalGraphData;
|
||||||
const v = gd.getVertexById(id);
|
let v = gd.getVertexById(id);
|
||||||
|
|
||||||
// Clear the array while keeping the references
|
// Clear the array while keeping the references
|
||||||
sources.length = 0;
|
sources.length = 0;
|
||||||
targets.length = 0;
|
targets.length = 0;
|
||||||
|
|
||||||
const possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
let possibleEdgeLabels = {} as any; // Collect all edge labels in a hashset
|
||||||
|
|
||||||
for (const p in v.inE) {
|
for (let p in v.inE) {
|
||||||
possibleEdgeLabels[p] = true;
|
possibleEdgeLabels[p] = true;
|
||||||
const edges = v.inE[p];
|
const edges = v.inE[p];
|
||||||
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
$.each(edges, (index: number, edge: GraphData.GremlinShortInEdge) => {
|
||||||
@@ -1629,7 +1629,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||||
sources.push({
|
sources.push({
|
||||||
name: caption,
|
name: caption,
|
||||||
id: neighborId,
|
id: neighborId,
|
||||||
@@ -1639,7 +1639,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const p in v.outE) {
|
for (let p in v.outE) {
|
||||||
possibleEdgeLabels[p] = true;
|
possibleEdgeLabels[p] = true;
|
||||||
const edges = v.outE[p];
|
const edges = v.outE[p];
|
||||||
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
$.each(edges, (index: number, edge: GraphData.GremlinShortOutEdge) => {
|
||||||
@@ -1648,7 +1648,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
// If id not known, it must be an edge node whose neighbor hasn't been loaded into the graph, yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
let caption = GraphData.GraphData.getNodePropValue(gd.getVertexById(neighborId), nodeCaption) as string;
|
||||||
targets.push({
|
targets.push({
|
||||||
name: caption,
|
name: caption,
|
||||||
id: neighborId,
|
id: neighborId,
|
||||||
@@ -1681,20 +1681,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedVertex = vertices[0];
|
let updatedVertex = vertices[0];
|
||||||
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
if (this.originalGraphData.hasVertexId(updatedVertex.id)) {
|
||||||
const currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
let currentVertex = this.originalGraphData.getVertexById(updatedVertex.id);
|
||||||
// Copy updated properties
|
// Copy updated properties
|
||||||
if (currentVertex.hasOwnProperty("properties")) {
|
if (currentVertex.hasOwnProperty("properties")) {
|
||||||
delete currentVertex["properties"];
|
delete currentVertex["properties"];
|
||||||
}
|
}
|
||||||
for (const p in updatedVertex) {
|
for (var p in updatedVertex) {
|
||||||
(currentVertex as any)[p] = updatedVertex[p];
|
(currentVertex as any)[p] = updatedVertex[p];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
// TODO This kind of assumes saveVertexProperty is done from property panes.
|
||||||
const hn = this.state.highlightedNode;
|
let hn = this.state.highlightedNode;
|
||||||
if (hn && hn.id === updatedVertex.id) {
|
if (hn && hn.id === updatedVertex.id) {
|
||||||
this.updatePropertiesPane(hn.id);
|
this.updatePropertiesPane(hn.id);
|
||||||
}
|
}
|
||||||
@@ -1708,7 +1708,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
igraphConfig?: IGraphConfig
|
igraphConfig?: IGraphConfig
|
||||||
) {
|
) {
|
||||||
this.originalGraphData = graphData;
|
this.originalGraphData = graphData;
|
||||||
const gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
let gd = JSON.parse(JSON.stringify(this.originalGraphData));
|
||||||
if (!this.d3ForceGraph) {
|
if (!this.d3ForceGraph) {
|
||||||
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
console.warn("Attempting to update graph, but d3ForceGraph not initialized, yet.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface GremlinSimpleClientParameters {
|
|||||||
password: string;
|
password: string;
|
||||||
successCallback: (result: Result) => void;
|
successCallback: (result: Result) => void;
|
||||||
progressCallback: (result: Result) => void;
|
progressCallback: (result: Result) => void;
|
||||||
failureCallback: (result: Result, error: string) => void;
|
failureCallback: (result: Result | null, error: string) => void;
|
||||||
infoCallback: (msg: string) => void;
|
infoCallback: (msg: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,14 +62,15 @@ export class GremlinSimpleClient {
|
|||||||
private static readonly requestChargeHeader = "x-ms-request-charge";
|
private static readonly requestChargeHeader = "x-ms-request-charge";
|
||||||
|
|
||||||
public params: GremlinSimpleClientParameters;
|
public params: GremlinSimpleClientParameters;
|
||||||
private protocols: string | string[];
|
private ws: WebSocket | undefined;
|
||||||
private ws: WebSocket;
|
|
||||||
|
|
||||||
public requestsToSend: { [requestId: string]: GremlinRequestMessage };
|
public requestsToSend: { [requestId: string]: GremlinRequestMessage };
|
||||||
public pendingRequests: { [requestId: string]: GremlinRequestMessage };
|
public pendingRequests: { [requestId: string]: GremlinRequestMessage };
|
||||||
|
|
||||||
constructor(params: GremlinSimpleClientParameters) {
|
constructor(params: GremlinSimpleClientParameters) {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
this.ws = undefined;
|
||||||
|
|
||||||
this.pendingRequests = {};
|
this.pendingRequests = {};
|
||||||
this.requestsToSend = {};
|
this.requestsToSend = {};
|
||||||
}
|
}
|
||||||
@@ -117,7 +118,7 @@ export class GremlinSimpleClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public decodeMessage(msg: MessageEvent): GremlinResponseMessage {
|
public decodeMessage(msg: MessageEvent): GremlinResponseMessage | null {
|
||||||
if (msg.data === null) {
|
if (msg.data === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -280,7 +281,7 @@ export class GremlinSimpleClient {
|
|||||||
|
|
||||||
public static utf8ToB64(utf8Str: string) {
|
public static utf8ToB64(utf8Str: string) {
|
||||||
return btoa(
|
return btoa(
|
||||||
encodeURIComponent(utf8Str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
encodeURIComponent(utf8Str).replace(/%([0-9A-F]{2})/g, function (_match, p1) {
|
||||||
return String.fromCharCode(parseInt(p1, 16));
|
return String.fromCharCode(parseInt(p1, 16));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -305,7 +306,7 @@ export class GremlinSimpleClient {
|
|||||||
return binaryMessage;
|
return binaryMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onOpen(event: any) {
|
private onOpen(_event: any) {
|
||||||
this.executeRequestsToSend();
|
this.executeRequestsToSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,6 +349,6 @@ export class GremlinSimpleClient {
|
|||||||
|
|
||||||
private sendGremlinMessage(gremlinRequestMessage: GremlinRequestMessage) {
|
private sendGremlinMessage(gremlinRequestMessage: GremlinRequestMessage) {
|
||||||
const gremlinFrame = GremlinSimpleClient.buildGremlinMessage(gremlinRequestMessage);
|
const gremlinFrame = GremlinSimpleClient.buildGremlinMessage(gremlinRequestMessage);
|
||||||
this.ws.send(gremlinFrame);
|
this.ws && this.ws.send(gremlinFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
|
|||||||
className={className}
|
className={className}
|
||||||
as="tr"
|
as="tr"
|
||||||
aria-label={node.caption}
|
aria-label={node.caption}
|
||||||
onActivated={() => this.props.onRootNodeSelected(node.id)}
|
onActivated={(e) => this.props.onRootNodeSelected(node.id)}
|
||||||
key={node.id}
|
key={node.id}
|
||||||
>
|
>
|
||||||
<td className="resultItem">
|
<td className="resultItem">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import React from "react";
|
||||||
import { mount, ReactWrapper } from "enzyme";
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
import * as Q from "q";
|
import * as Q from "q";
|
||||||
import React from "react";
|
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
|
||||||
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
|
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
|
||||||
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
|
|
||||||
|
|
||||||
describe("Property pane", () => {
|
describe("Property pane", () => {
|
||||||
const title = "My Title";
|
const title = "My Title";
|
||||||
@@ -37,25 +37,17 @@ describe("Property pane", () => {
|
|||||||
return {
|
return {
|
||||||
expandedTitle: title,
|
expandedTitle: title,
|
||||||
isCollapsed: false,
|
isCollapsed: false,
|
||||||
onCollapsedChanged: (): void => {
|
onCollapsedChanged: (newValue: boolean): void => {},
|
||||||
("");
|
|
||||||
},
|
|
||||||
node: highlightedNode,
|
node: highlightedNode,
|
||||||
getPkIdFromNodeData: (): string => undefined,
|
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
|
||||||
collectionPartitionKeyProperty: undefined,
|
collectionPartitionKeyProperty: null,
|
||||||
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
|
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
|
||||||
selectNode: (): void => {
|
selectNode: (id: string): void => {},
|
||||||
("");
|
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
|
||||||
},
|
possibleEdgeLabels: null,
|
||||||
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
|
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
|
||||||
possibleEdgeLabels: undefined,
|
deleteHighlightedNode: (): void => {},
|
||||||
editGraphEdges: (): Q.Promise<unknown> => Q.resolve(),
|
onModeChanged: (newMode: Mode): void => {},
|
||||||
deleteHighlightedNode: (): void => {
|
|
||||||
("");
|
|
||||||
},
|
|
||||||
onModeChanged: (): void => {
|
|
||||||
("");
|
|
||||||
},
|
|
||||||
viewMode: Mode.READONLY_PROP,
|
viewMode: Mode.READONLY_PROP,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export interface NodePropertiesComponentProps {
|
|||||||
selectNode: (id: string) => void;
|
selectNode: (id: string) => void;
|
||||||
updatePossibleVertices: () => Q.Promise<PossibleVertex[]>;
|
updatePossibleVertices: () => Q.Promise<PossibleVertex[]>;
|
||||||
possibleEdgeLabels: Item[];
|
possibleEdgeLabels: Item[];
|
||||||
editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<unknown>;
|
editGraphEdges: (editedEdges: EditedEdges) => Q.Promise<any>;
|
||||||
deleteHighlightedNode: () => void;
|
deleteHighlightedNode: () => void;
|
||||||
onModeChanged: (newMode: Mode) => void;
|
onModeChanged: (newMode: Mode) => void;
|
||||||
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it
|
viewMode: Mode; // If viewMode is specified in parent, keep state in sync with it
|
||||||
@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
editedProperties: {
|
editedProperties: {
|
||||||
pkId: undefined,
|
pkId: null,
|
||||||
readOnlyProperties: [],
|
readOnlyProperties: [],
|
||||||
existingProperties: [],
|
existingProperties: [],
|
||||||
addedProperties: [],
|
addedProperties: [],
|
||||||
@@ -98,12 +98,15 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
|
public static getDerivedStateFromProps(
|
||||||
|
props: NodePropertiesComponentProps,
|
||||||
|
state: NodePropertiesComponentState
|
||||||
|
): Partial<NodePropertiesComponentState> {
|
||||||
if (props.viewMode !== Mode.READONLY_PROP) {
|
if (props.viewMode !== Mode.READONLY_PROP) {
|
||||||
return { isDeleteConfirm: false };
|
return { isDeleteConfirm: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
@@ -134,14 +137,11 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
* Get type option. Limit to string, number or boolean
|
* Get type option. Limit to string, number or boolean
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
private static getTypeOption(
|
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
|
||||||
value: null | string | number | undefined | boolean
|
if (value == null) {
|
||||||
): ViewModels.InputPropertyValueTypeString {
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
if (value === null) {
|
|
||||||
return "null";
|
return "null";
|
||||||
}
|
}
|
||||||
const type = typeof value;
|
let type = typeof value;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "number":
|
case "number":
|
||||||
case "boolean":
|
case "boolean":
|
||||||
@@ -173,10 +173,9 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
|
|
||||||
const existingProps: ViewModels.InputProperty[] = [];
|
const existingProps: ViewModels.InputProperty[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (this.props.node.hasOwnProperty("properties")) {
|
if (this.props.node.hasOwnProperty("properties")) {
|
||||||
const hProps = this.props.node["properties"];
|
const hProps = this.props.node["properties"];
|
||||||
for (const p in hProps) {
|
for (let p in hProps) {
|
||||||
const propValues = hProps[p];
|
const propValues = hProps[p];
|
||||||
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
|
||||||
key: p,
|
key: p,
|
||||||
@@ -438,7 +437,7 @@ export class NodePropertiesComponent extends React.Component<
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus("connectionStatus"));
|
|
||||||
|
|
||||||
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,9 +80,8 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
|
|
||||||
notebookButtons.push(createOpenTerminalButton(container));
|
notebookButtons.push(createOpenTerminalButton(container));
|
||||||
if (userContext.features.phoenix === false) {
|
|
||||||
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
notebookButtons.push(createNotebookWorkspaceResetButton(container));
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
(userContext.apiType === "Mongo" &&
|
(userContext.apiType === "Mongo" &&
|
||||||
useNotebook.getState().isShellEnabled &&
|
useNotebook.getState().isShellEnabled &&
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { StyleConstants } from "../../../Common/Constants";
|
|||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
|
||||||
import { MemoryTracker } from "./MemoryTrackerComponent";
|
import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -202,10 +201,3 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
|||||||
onRender: () => <MemoryTracker />,
|
onRender: () => <MemoryTracker />,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createConnectionStatus = (key: string): ICommandBarItemProps => {
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
onRender: () => <ConnectionStatus />,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
|
||||||
|
|
||||||
.connectionStatusContainer {
|
|
||||||
cursor: default;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0 9px;
|
|
||||||
border: 1px;
|
|
||||||
min-height: 44px;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
padding-right: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: @DataExplorerFont;
|
|
||||||
color: @DefaultFontColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.connectionStatusFailed{
|
|
||||||
color: #bd1919;
|
|
||||||
}
|
|
||||||
.ring-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ringringGreen {
|
|
||||||
border: 3px solid green;
|
|
||||||
border-radius: 30px;
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
position: absolute;
|
|
||||||
margin: .4285em 0em 0em 0.07477em;
|
|
||||||
animation: pulsate 3s ease-out;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
opacity: 0.0
|
|
||||||
}
|
|
||||||
.ringringYellow{
|
|
||||||
border: 3px solid #ffbf00;
|
|
||||||
border-radius: 30px;
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
position: absolute;
|
|
||||||
margin: .4285em 0em 0em 0.07477em;
|
|
||||||
animation: pulsate 3s ease-out;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
opacity: 0.0
|
|
||||||
}
|
|
||||||
.ringringRed{
|
|
||||||
border: 3px solid #bd1919;
|
|
||||||
border-radius: 30px;
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
position: absolute;
|
|
||||||
margin: .4285em 0em 0em 0.07477em;
|
|
||||||
animation: pulsate 3s ease-out;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
opacity: 0.0
|
|
||||||
}
|
|
||||||
@keyframes pulsate {
|
|
||||||
0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;}
|
|
||||||
15% {opacity: 0.8;}
|
|
||||||
25% {opacity: 0.6;}
|
|
||||||
45% {opacity: 0.4;}
|
|
||||||
70% {opacity: 0.3;}
|
|
||||||
100% {-webkit-transform: scale(.7, .7); opacity: 0.1;}
|
|
||||||
}
|
|
||||||
.locationGreenDot{
|
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 0.07em;
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
.locationYellowDot{
|
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 0.07em;
|
|
||||||
color: #ffbf00;
|
|
||||||
}
|
|
||||||
.locationRedDot{
|
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 0.07em;
|
|
||||||
color: #bd1919;
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import { Icon, ProgressIndicator, Spinner, SpinnerSize, Stack, TooltipHost } from "@fluentui/react";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ConnectionStatusType } from "../../../Common/Constants";
|
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
|
||||||
import "../CommandBar/ConnectionStatusComponent.less";
|
|
||||||
|
|
||||||
export const ConnectionStatus: React.FC = (): JSX.Element => {
|
|
||||||
const [second, setSecond] = React.useState("00");
|
|
||||||
const [minute, setMinute] = React.useState("00");
|
|
||||||
const [isActive, setIsActive] = React.useState(false);
|
|
||||||
const [counter, setCounter] = React.useState(0);
|
|
||||||
const [statusColor, setStatusColor] = React.useState("locationYellowDot");
|
|
||||||
const [statusColorAnimation, setStatusColorAnimation] = React.useState("ringringYellow");
|
|
||||||
const toolTipContent = "Hosted runtime status.";
|
|
||||||
React.useEffect(() => {
|
|
||||||
let intervalId: NodeJS.Timeout;
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
intervalId = setInterval(() => {
|
|
||||||
const secondCounter = counter % 60;
|
|
||||||
const minuteCounter = Math.floor(counter / 60);
|
|
||||||
const computedSecond: string = String(secondCounter).length === 1 ? `0${secondCounter}` : `${secondCounter}`;
|
|
||||||
const computedMinute: string = String(minuteCounter).length === 1 ? `0${minuteCounter}` : `${minuteCounter}`;
|
|
||||||
|
|
||||||
setSecond(computedSecond);
|
|
||||||
setMinute(computedMinute);
|
|
||||||
|
|
||||||
setCounter((counter) => counter + 1);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
return () => clearInterval(intervalId);
|
|
||||||
}, [isActive, counter]);
|
|
||||||
|
|
||||||
const stopTimer = () => {
|
|
||||||
setIsActive(false);
|
|
||||||
setCounter(0);
|
|
||||||
setSecond("00");
|
|
||||||
setMinute("00");
|
|
||||||
};
|
|
||||||
|
|
||||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
|
||||||
if (!connectionInfo) {
|
|
||||||
return (
|
|
||||||
<Stack className="connectionStatusContainer" horizontal>
|
|
||||||
<span>Connecting</span>
|
|
||||||
<Spinner size={SpinnerSize.medium} />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (connectionInfo && connectionInfo.status === ConnectionStatusType.Allocating && isActive === false) {
|
|
||||||
setIsActive(true);
|
|
||||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Connected && isActive === true) {
|
|
||||||
stopTimer();
|
|
||||||
setStatusColor("locationGreenDot");
|
|
||||||
setStatusColorAnimation("ringringGreen");
|
|
||||||
} else if (connectionInfo && connectionInfo.status === ConnectionStatusType.Failed && isActive === true) {
|
|
||||||
stopTimer();
|
|
||||||
setStatusColor("locationRedDot");
|
|
||||||
setStatusColorAnimation("ringringRed");
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TooltipHost content={toolTipContent}>
|
|
||||||
<Stack className="connectionStatusContainer" horizontal>
|
|
||||||
<div className="ring-container">
|
|
||||||
<div className={statusColorAnimation}></div>
|
|
||||||
<Icon iconName="LocationDot" className={statusColor} />
|
|
||||||
</div>
|
|
||||||
<span className={connectionInfo.status === ConnectionStatusType.Failed ? "connectionStatusFailed" : ""}>
|
|
||||||
{connectionInfo.status}
|
|
||||||
</span>
|
|
||||||
{connectionInfo.status === ConnectionStatusType.Allocating && isActive && (
|
|
||||||
<ProgressIndicator description={minute + ":" + second} />
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</TooltipHost>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -109,7 +109,7 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
|
|||||||
const q = params.toString();
|
const q = params.toString();
|
||||||
const suffix = q !== "" ? `?${q}` : "";
|
const suffix = q !== "" ? `?${q}` : "";
|
||||||
|
|
||||||
const url = (serverConfig.endpoint.slice(0, -1) || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
const url = (serverConfig.endpoint || "") + `api/kernels/${kernelId}/channels${suffix}`;
|
||||||
|
|
||||||
return url.replace(/^http(s)?/, "ws$1");
|
return url.replace(/^http(s)?/, "ws$1");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class NotebookContainerClient {
|
|||||||
|
|
||||||
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
const { notebookServerEndpoint, authToken } = this.getNotebookServerConfig();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${notebookServerEndpoint}api/metrics/memory`, {
|
const response = await fetch(`${notebookServerEndpoint}/api/metrics/memory`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: authToken,
|
Authorization: authToken,
|
||||||
|
|||||||
@@ -36,10 +36,7 @@ export class NotebookContentClient {
|
|||||||
*
|
*
|
||||||
* @param parent parent folder
|
* @param parent parent folder
|
||||||
*/
|
*/
|
||||||
public async createNewNotebookFile(
|
public createNewNotebookFile(parent: NotebookContentItem, isGithubTree?: boolean): Promise<NotebookContentItem> {
|
||||||
parent: NotebookContentItem,
|
|
||||||
isGithubTree?: boolean
|
|
||||||
): Promise<NotebookContentItem> {
|
|
||||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||||
throw new Error(`Parent must be a directory: ${parent}`);
|
throw new Error(`Parent must be a directory: ${parent}`);
|
||||||
}
|
}
|
||||||
@@ -102,6 +99,7 @@ export class NotebookContentClient {
|
|||||||
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
if (!parent || parent.type !== NotebookContentItemType.Directory) {
|
||||||
throw new Error(`Parent must be a directory: ${parent}`);
|
throw new Error(`Parent must be a directory: ${parent}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filepath = NotebookUtil.getFilePath(parent.path, name);
|
const filepath = NotebookUtil.getFilePath(parent.path, name);
|
||||||
if (await this.checkIfFilepathExists(filepath)) {
|
if (await this.checkIfFilepathExists(filepath)) {
|
||||||
throw new Error(`File already exists: ${filepath}`);
|
throw new Error(`File already exists: ${filepath}`);
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ interface NotebookState {
|
|||||||
myNotebooksContentRoot: NotebookContentItem;
|
myNotebooksContentRoot: NotebookContentItem;
|
||||||
gitHubNotebooksContentRoot: NotebookContentItem;
|
gitHubNotebooksContentRoot: NotebookContentItem;
|
||||||
galleryContentRoot: NotebookContentItem;
|
galleryContentRoot: NotebookContentItem;
|
||||||
connectionInfo: DataModels.ContainerConnectionInfo;
|
|
||||||
NotebookFolderName: string;
|
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => void;
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => void;
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => void;
|
||||||
@@ -45,7 +43,6 @@ interface NotebookState {
|
|||||||
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
|
||||||
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
|
||||||
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
|
||||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||||
@@ -68,8 +65,6 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
myNotebooksContentRoot: undefined,
|
myNotebooksContentRoot: undefined,
|
||||||
gitHubNotebooksContentRoot: undefined,
|
gitHubNotebooksContentRoot: undefined,
|
||||||
galleryContentRoot: undefined,
|
galleryContentRoot: undefined,
|
||||||
connectionInfo: undefined,
|
|
||||||
NotebookFolderName: userContext.features.phoenix ? "My Notebooks Scratch" : "My Notebooks",
|
|
||||||
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
setIsNotebookEnabled: (isNotebookEnabled: boolean) => set({ isNotebookEnabled }),
|
||||||
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
setIsNotebooksEnabledForAccount: (isNotebooksEnabledForAccount: boolean) => set({ isNotebooksEnabledForAccount }),
|
||||||
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
setNotebookServerInfo: (notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo) =>
|
||||||
@@ -174,7 +169,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
},
|
},
|
||||||
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
|
||||||
const myNotebooksContentRoot = {
|
const myNotebooksContentRoot = {
|
||||||
name: get().NotebookFolderName,
|
name: "My Notebooks",
|
||||||
path: get().notebookBasePath,
|
path: get().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
@@ -183,11 +178,13 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
path: "Gallery",
|
path: "Gallery",
|
||||||
type: NotebookContentItemType.File,
|
type: NotebookContentItemType.File,
|
||||||
};
|
};
|
||||||
const gitHubNotebooksContentRoot = {
|
const gitHubNotebooksContentRoot = notebookManager?.gitHubOAuthService?.isLoggedIn()
|
||||||
name: "GitHub repos",
|
? {
|
||||||
path: "PsuedoDir",
|
name: "GitHub repos",
|
||||||
type: NotebookContentItemType.Directory,
|
path: "PsuedoDir",
|
||||||
};
|
type: NotebookContentItemType.Directory,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
set({
|
set({
|
||||||
myNotebooksContentRoot,
|
myNotebooksContentRoot,
|
||||||
galleryContentRoot,
|
galleryContentRoot,
|
||||||
@@ -249,5 +246,4 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
set({ gitHubNotebooksContentRoot });
|
set({ gitHubNotebooksContentRoot });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setConnectionInfo: (connectionInfo: DataModels.ContainerConnectionInfo) => set({ connectionInfo }),
|
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils
|
|||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@@ -76,8 +75,6 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
|||||||
selectedLocation.owner,
|
selectedLocation.owner,
|
||||||
selectedLocation.repo
|
selectedLocation.repo
|
||||||
)} - ${selectedLocation.branch}`;
|
)} - ${selectedLocation.branch}`;
|
||||||
} else if (selectedLocation.type === "MyNotebooks" && userContext.features.phoenix) {
|
|
||||||
destination = "My Notebooks Scratch";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
import React, { FormEvent, FunctionComponent } from "react";
|
import React, { FormEvent, FunctionComponent } from "react";
|
||||||
import { IPinnedRepo } from "../../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../../Juno/JunoClient";
|
||||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
import { useNotebook } from "../../Notebook/useNotebook";
|
|
||||||
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
|
|
||||||
interface Location {
|
interface Location {
|
||||||
@@ -47,10 +46,11 @@ export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps>
|
|||||||
|
|
||||||
const getDropDownOptions = (): IDropdownOption[] => {
|
const getDropDownOptions = (): IDropdownOption[] => {
|
||||||
const options: IDropdownOption[] = [];
|
const options: IDropdownOption[] = [];
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
key: "MyNotebooks-Item",
|
key: "MyNotebooks-Item",
|
||||||
text: useNotebook.getState().NotebookFolderName,
|
text: ResourceTreeAdapter.MyNotebooksTitle,
|
||||||
title: useNotebook.getState().NotebookFolderName,
|
title: ResourceTreeAdapter.MyNotebooksTitle,
|
||||||
data: {
|
data: {
|
||||||
type: "MyNotebooks",
|
type: "MyNotebooks",
|
||||||
} as Location,
|
} as Location,
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"phoenixClient": PhoenixClient {},
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"phoenixClient": PhoenixClient {},
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function createDataTable($dataTableElem: JQuery, settings: any): DataTabl
|
|||||||
* @return The given settings with all columns having a rendering function
|
* @return The given settings with all columns having a rendering function
|
||||||
*/
|
*/
|
||||||
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
||||||
let tableColumns: DataTables.ColumnLegacy[] = null;
|
var tableColumns: DataTables.ColumnLegacy[] = null;
|
||||||
|
|
||||||
if (settings.aoColumns) {
|
if (settings.aoColumns) {
|
||||||
tableColumns = settings.aoColumns;
|
tableColumns = settings.aoColumns;
|
||||||
@@ -34,7 +34,7 @@ function applyDefaultRendering(settings: any): DataTables.SettingsLegacy {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < tableColumns.length; i++) {
|
for (var i = 0; i < tableColumns.length; i++) {
|
||||||
// the column does not have a render function
|
// the column does not have a render function
|
||||||
if (!tableColumns[i].mRender) {
|
if (!tableColumns[i].mRender) {
|
||||||
tableColumns[i].mRender = defaultDataRender;
|
tableColumns[i].mRender = defaultDataRender;
|
||||||
|
|||||||
@@ -131,14 +131,14 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
if (myNotebooksContentRoot) {
|
if (myNotebooksContentRoot) {
|
||||||
notebooksTree.children.push(buildMyNotebooksTree());
|
notebooksTree.children.push(buildMyNotebooksTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||||
// collapse all other notebook nodes
|
// collapse all other notebook nodes
|
||||||
notebooksTree.children.forEach((node) => (node.isExpanded = false));
|
notebooksTree.children.forEach((node) => (node.isExpanded = false));
|
||||||
notebooksTree.children.push(buildGitHubNotebooksTree(true));
|
notebooksTree.children.push(buildGitHubNotebooksTree());
|
||||||
} else if (container.notebookManager && !container.notebookManager.gitHubOAuthService.isLoggedIn()) {
|
|
||||||
notebooksTree.children.push(buildGitHubNotebooksTree(false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notebooksTree;
|
return notebooksTree;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
return myNotebooksTree;
|
return myNotebooksTree;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildGitHubNotebooksTree = (isConnected: boolean): TreeNode => {
|
const buildGitHubNotebooksTree = (): TreeNode => {
|
||||||
const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode(
|
||||||
gitHubNotebooksContentRoot,
|
gitHubNotebooksContentRoot,
|
||||||
(item: NotebookContentItem) => {
|
(item: NotebookContentItem) => {
|
||||||
@@ -190,7 +190,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const manageGitContextMenu: TreeNodeMenuItem[] = [
|
|
||||||
|
gitHubNotebooksTree.contextMenu = [
|
||||||
{
|
{
|
||||||
label: "Manage GitHub settings",
|
label: "Manage GitHub settings",
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
@@ -215,23 +216,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const connectGitContextMenu: TreeNodeMenuItem[] = [
|
|
||||||
{
|
|
||||||
label: "Connect to GitHub",
|
|
||||||
onClick: () =>
|
|
||||||
useSidePanel
|
|
||||||
.getState()
|
|
||||||
.openSidePanel(
|
|
||||||
"Connect to GitHub",
|
|
||||||
<GitHubReposPanel
|
|
||||||
explorer={container}
|
|
||||||
gitHubClientProp={container.notebookManager.gitHubClient}
|
|
||||||
junoClientProp={container.notebookManager.junoClient}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
gitHubNotebooksTree.contextMenu = isConnected ? manageGitContextMenu : connectGitContextMenu;
|
|
||||||
gitHubNotebooksTree.isExpanded = true;
|
gitHubNotebooksTree.isExpanded = true;
|
||||||
gitHubNotebooksTree.isAlphaSorted = true;
|
gitHubNotebooksTree.isAlphaSorted = true;
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import UserDefinedFunction from "./UserDefinedFunction";
|
|||||||
|
|
||||||
export class ResourceTreeAdapter implements ReactAdapter {
|
export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
public static readonly MyNotebooksTitle = "My Notebooks";
|
public static readonly MyNotebooksTitle = "My Notebooks";
|
||||||
public static readonly MyNotebooksScratchTitle = "My Notebooks Scratch";
|
|
||||||
public static readonly GitHubReposTitle = "GitHub repos";
|
public static readonly GitHubReposTitle = "GitHub repos";
|
||||||
|
|
||||||
private static readonly DataTitle = "DATA";
|
private static readonly DataTitle = "DATA";
|
||||||
@@ -131,8 +130,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
path: "Gallery",
|
path: "Gallery",
|
||||||
type: NotebookContentItemType.File,
|
type: NotebookContentItemType.File,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.myNotebooksContentRoot = {
|
this.myNotebooksContentRoot = {
|
||||||
name: useNotebook.getState().NotebookFolderName,
|
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||||
path: useNotebook.getState().notebookBasePath,
|
path: useNotebook.getState().notebookBasePath,
|
||||||
type: NotebookContentItemType.Directory,
|
type: NotebookContentItemType.Directory,
|
||||||
};
|
};
|
||||||
@@ -146,11 +146,16 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.gitHubNotebooksContentRoot = {
|
|
||||||
name: ResourceTreeAdapter.GitHubReposTitle,
|
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||||
path: ResourceTreeAdapter.PseudoDirPath,
|
this.gitHubNotebooksContentRoot = {
|
||||||
type: NotebookContentItemType.Directory,
|
name: ResourceTreeAdapter.GitHubReposTitle,
|
||||||
};
|
path: ResourceTreeAdapter.PseudoDirPath,
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.gitHubNotebooksContentRoot = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.all(refreshTasks);
|
return Promise.all(refreshTasks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
|||||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||||
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
||||||
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import "./Explorer/Menus/CommandBar/ConnectionStatusComponent.less";
|
|
||||||
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
|
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
|
||||||
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
|
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
|
||||||
import { NotificationConsole } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { NotificationConsole } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes } from "../Common/Constants";
|
|
||||||
import { configContext } from "../ConfigContext";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
|
||||||
|
|
||||||
export interface IPhoenixResponse<T> {
|
|
||||||
status: number;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
export interface IPhoenixConnectionInfoResult {
|
|
||||||
readonly notebookAuthToken?: string;
|
|
||||||
readonly notebookServerUrl?: string;
|
|
||||||
}
|
|
||||||
export interface IProvosionData {
|
|
||||||
cosmosEndpoint: string;
|
|
||||||
resourceId: string;
|
|
||||||
dbAccountName: string;
|
|
||||||
aadToken: string;
|
|
||||||
resourceGroup: string;
|
|
||||||
subscriptionId: string;
|
|
||||||
}
|
|
||||||
export class PhoenixClient {
|
|
||||||
public async containerConnectionInfo(
|
|
||||||
provisionData: IProvosionData
|
|
||||||
): Promise<IPhoenixResponse<IPhoenixConnectionInfoResult>> {
|
|
||||||
const response = await window.fetch(`${this.getPhoenixContainerPoolingEndPoint()}/provision`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: PhoenixClient.getHeaders(),
|
|
||||||
body: JSON.stringify(provisionData),
|
|
||||||
});
|
|
||||||
let data: IPhoenixConnectionInfoResult;
|
|
||||||
if (response.status === HttpStatusCodes.OK) {
|
|
||||||
data = await response.json();
|
|
||||||
} else {
|
|
||||||
const connectionStatus: DataModels.ContainerConnectionInfo = {
|
|
||||||
status: ConnectionStatusType.Failed,
|
|
||||||
};
|
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: response.status,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getPhoenixEndpoint(): string {
|
|
||||||
const phoenixEndpoint = userContext.features.junoEndpoint ?? configContext.JUNO_ENDPOINT;
|
|
||||||
if (configContext.allowedJunoOrigins.indexOf(new URL(phoenixEndpoint).origin) === -1) {
|
|
||||||
const error = `${phoenixEndpoint} not allowed as juno endpoint`;
|
|
||||||
console.error(error);
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return phoenixEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPhoenixContainerPoolingEndPoint(): string {
|
|
||||||
return `${PhoenixClient.getPhoenixEndpoint()}/api/containerpooling`;
|
|
||||||
}
|
|
||||||
private static getHeaders(): HeadersInit {
|
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
|
||||||
return {
|
|
||||||
[authorizationHeader.header]: authorizationHeader.token,
|
|
||||||
[HttpHeaders.contentType]: "application/json",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ export type Features = {
|
|||||||
autoscaleDefault: boolean;
|
autoscaleDefault: boolean;
|
||||||
partitionKeyDefault: boolean;
|
partitionKeyDefault: boolean;
|
||||||
partitionKeyDefault2: boolean;
|
partitionKeyDefault2: boolean;
|
||||||
phoenix: boolean;
|
|
||||||
readonly enableSDKoperations: boolean;
|
readonly enableSDKoperations: boolean;
|
||||||
readonly enableSpark: boolean;
|
readonly enableSpark: boolean;
|
||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
@@ -77,6 +76,5 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
partitionKeyDefault: "true" === get("partitionkeytest"),
|
partitionKeyDefault: "true" === get("partitionkeytest"),
|
||||||
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
|
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
|
||||||
notebooksTemporarilyDown: "true" === get("notebookstemporarilydown", "true"),
|
notebooksTemporarilyDown: "true" === get("notebookstemporarilydown", "true"),
|
||||||
phoenix: "true" === get("phoenix"),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
SortBy,
|
SortBy,
|
||||||
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
|
||||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import { trace, traceFailure, traceStart, traceSuccess } from "../Shared/Telemetry/TelemetryProcessor";
|
import { trace, traceFailure, traceStart, traceSuccess } from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -224,13 +223,11 @@ export function downloadItem(
|
|||||||
|
|
||||||
const name = data.name;
|
const name = data.name;
|
||||||
useDialog.getState().showOkCancelModalDialog(
|
useDialog.getState().showOkCancelModalDialog(
|
||||||
`Download to ${useNotebook.getState().NotebookFolderName}`,
|
"Download to My Notebooks",
|
||||||
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
|
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
|
||||||
"Download",
|
"Download",
|
||||||
async () => {
|
async () => {
|
||||||
const clearInProgressMessage = logConsoleProgress(
|
const clearInProgressMessage = logConsoleProgress(`Downloading ${name} to My Notebooks`);
|
||||||
`Downloading ${name} to ${useNotebook.getState().NotebookFolderName}`
|
|
||||||
);
|
|
||||||
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
const startKey = traceStart(Action.NotebooksGalleryDownload, {
|
||||||
notebookId: data.id,
|
notebookId: data.id,
|
||||||
downloadCount: data.downloads,
|
downloadCount: data.downloads,
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export function useAADAuth(): ReturnType {
|
|||||||
});
|
});
|
||||||
setTenantId(response.tenantId);
|
setTenantId(response.tenantId);
|
||||||
setAccount(response.account);
|
setAccount(response.account);
|
||||||
localStorage.setItem("cachedTenantId", response.tenantId);
|
|
||||||
},
|
},
|
||||||
[account, tenantId]
|
[account, tenantId]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -98,11 +98,9 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
|||||||
const msalInstance = getMsalInstance();
|
const msalInstance = getMsalInstance();
|
||||||
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
const cachedAccount = msalInstance.getAllAccounts()?.[0];
|
||||||
msalInstance.setActiveAccount(cachedAccount);
|
msalInstance.setActiveAccount(cachedAccount);
|
||||||
const cachedTenantId = localStorage.getItem("cachedTenantId");
|
|
||||||
const aadTokenResponse = await msalInstance.acquireTokenSilent({
|
const aadTokenResponse = await msalInstance.acquireTokenSilent({
|
||||||
forceRefresh: true,
|
forceRefresh: true,
|
||||||
scopes: [hrefEndpoint],
|
scopes: [hrefEndpoint],
|
||||||
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
|
|
||||||
});
|
});
|
||||||
aadToken = aadTokenResponse.accessToken;
|
aadToken = aadTokenResponse.accessToken;
|
||||||
}
|
}
|
||||||
@@ -339,9 +337,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
|||||||
if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) {
|
if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) {
|
||||||
userContext.features.partitionKeyDefault2 = true;
|
userContext.features.partitionKeyDefault2 = true;
|
||||||
}
|
}
|
||||||
if (inputs.flights.indexOf(Flights.Phoenix) !== -1) {
|
|
||||||
userContext.features.phoenix = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"noUnusedParameters": true
|
"noUnusedParameters": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"./src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts",
|
||||||
"./src/AuthType.ts",
|
"./src/AuthType.ts",
|
||||||
"./src/Bindings/ReactBindingHandler.ts",
|
"./src/Bindings/ReactBindingHandler.ts",
|
||||||
"./src/Common/ArrayHashMap.ts",
|
"./src/Common/ArrayHashMap.ts",
|
||||||
@@ -165,4 +166,4 @@
|
|||||||
"src/Terminal/**/*",
|
"src/Terminal/**/*",
|
||||||
"src/Utils/arm/**/*"
|
"src/Utils/arm/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user