mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 09:51:11 +00:00
Migrate new graph vertex panel to react (#702)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
|
||||
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
|
||||
<div class="contextual-pane" data-bind="attr: { id: id }">
|
||||
<!-- New Vertex form - Start -->
|
||||
<div class="contextual-pane-in">
|
||||
<form class="paneContentContainer" data-bind="submit: submit">
|
||||
<!-- New Vertex header - Start -->
|
||||
<div class="firstdivbg headerline">
|
||||
<span role="heading" aria-level="2">New Vertex</span>
|
||||
<div
|
||||
class="closeImg"
|
||||
role="button"
|
||||
aria-label="Close pane"
|
||||
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
|
||||
tabindex="0"
|
||||
>
|
||||
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- New Vertex header - End -->
|
||||
|
||||
<!-- New Vertex errors - Start -->
|
||||
<div
|
||||
aria-live="assertive"
|
||||
class="warningErrorContainer"
|
||||
data-bind="visible: formErrors() && formErrors() !== ''"
|
||||
>
|
||||
<div class="warningErrorContent">
|
||||
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error" /></span>
|
||||
<span class="warningErrorDetailsLinkContainer">
|
||||
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
|
||||
<a
|
||||
class="errorLink"
|
||||
role="link"
|
||||
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '' , click: showErrorDetails, event: { keypress: onMoreDetailsKeyPress }"
|
||||
tabindex="0"
|
||||
>
|
||||
More details
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- New Vertex errors - End -->
|
||||
|
||||
<!-- New Vertex inputs - Start -->
|
||||
<div class="paneMainContent">
|
||||
<new-vertex-form
|
||||
class="newvertexContainer"
|
||||
params="{ newVertexData: tempVertexData, firstFieldHasFocus: firstFieldHasFocus, partitionKeyProperty: partitionKeyProperty }"
|
||||
></new-vertex-form>
|
||||
</div>
|
||||
<div class="paneFooter">
|
||||
<div class="leftpanel-okbut"><input type="submit" value="OK" class="btncreatecoll1" /></div>
|
||||
</div>
|
||||
<!-- New Vertex inputs - End -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- New Vertex form - End -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +0,0 @@
|
||||
@import "../../../less/Common/Constants";
|
||||
|
||||
.newvertexContainer {
|
||||
height:100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||
import { KeyCodes } from "../../Common/Constants";
|
||||
import Explorer from "../Explorer";
|
||||
|
||||
export default class NewVertexPane extends ContextualPaneBase {
|
||||
public container: Explorer;
|
||||
public visible: ko.Observable<boolean>;
|
||||
public formErrors: ko.Observable<string>;
|
||||
public formErrorsDetails: ko.Observable<string>;
|
||||
|
||||
// Graph style stuff
|
||||
public tempVertexData: ko.Observable<ViewModels.NewVertexData>; // vertex data being edited
|
||||
private onSubmitCreateCallback: (newVertexData: ViewModels.NewVertexData) => void;
|
||||
private partitionKeyProperty: ko.Observable<string>;
|
||||
|
||||
constructor(options: ViewModels.PaneOptions) {
|
||||
super(options);
|
||||
this.tempVertexData = ko.observable<ViewModels.NewVertexData>(null);
|
||||
this.partitionKeyProperty = ko.observable(null);
|
||||
this.resetData();
|
||||
}
|
||||
|
||||
public submit() {
|
||||
// Commit edited changes
|
||||
if (this.onSubmitCreateCallback != null) {
|
||||
this.onSubmitCreateCallback(this.tempVertexData());
|
||||
}
|
||||
|
||||
// this.close();
|
||||
}
|
||||
|
||||
public resetData() {
|
||||
super.resetData();
|
||||
|
||||
this.onSubmitCreateCallback = null;
|
||||
|
||||
this.tempVertexData({
|
||||
label: "",
|
||||
properties: <ViewModels.InputProperty[]>[],
|
||||
});
|
||||
this.partitionKeyProperty(null);
|
||||
}
|
||||
|
||||
public subscribeOnSubmitCreate(callback: (newVertexData: ViewModels.NewVertexData) => void): void {
|
||||
this.onSubmitCreateCallback = callback;
|
||||
}
|
||||
|
||||
public setPartitionKeyProperty(pKeyProp: string): void {
|
||||
this.partitionKeyProperty(pKeyProp);
|
||||
}
|
||||
|
||||
public onMoreDetailsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) {
|
||||
this.showErrorDetails();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public buildString = (prefix: string, index: number): string => {
|
||||
return `${prefix}${index}`;
|
||||
};
|
||||
}
|
||||
10
src/Explorer/Panes/NewVertexPanel/NewVertexPanel.less
Normal file
10
src/Explorer/Panes/NewVertexPanel/NewVertexPanel.less
Normal file
@@ -0,0 +1,10 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.newvertexContainer {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
}
|
||||
78
src/Explorer/Panes/NewVertexPanel/NewVertexPanel.test.tsx
Normal file
78
src/Explorer/Panes/NewVertexPanel/NewVertexPanel.test.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { shallow, ShallowWrapper } from "enzyme";
|
||||
import React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NewVertexPanel } from "./NewVertexPanel";
|
||||
|
||||
describe("New Vertex Panel", () => {
|
||||
let fakeExplorer: Explorer;
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeExplorer = new Explorer();
|
||||
});
|
||||
|
||||
it("should render default property", () => {
|
||||
const props = {
|
||||
explorer: fakeExplorer,
|
||||
partitionKeyPropertyProp: "",
|
||||
onSubmit: (): void => undefined,
|
||||
openNotificationConsole: (): void => undefined,
|
||||
};
|
||||
wrapper = shallow(<NewVertexPanel {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render button in footer", () => {
|
||||
const button = wrapper.find("PrimaryButton").first();
|
||||
expect(button).toBeDefined();
|
||||
});
|
||||
|
||||
it("should render form", () => {
|
||||
const form = wrapper.find("form").first();
|
||||
expect(form).toBeDefined();
|
||||
});
|
||||
|
||||
it("should call form submit method", () => {
|
||||
const onSubmitSpy = jest.fn();
|
||||
|
||||
const newWrapper = shallow(
|
||||
<NewVertexPanel
|
||||
explorer={fakeExplorer}
|
||||
partitionKeyPropertyProp={undefined}
|
||||
openNotificationConsole={(): void => undefined}
|
||||
onSubmit={onSubmitSpy}
|
||||
/>
|
||||
);
|
||||
//eslint-disable-next-line
|
||||
newWrapper.find("form").simulate("submit", { preventDefault: () => {} });
|
||||
|
||||
expect(onSubmitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call error and success scenario method", () => {
|
||||
const onSubmitSpy = jest.fn();
|
||||
const onErrorSpy = jest.fn();
|
||||
const onSuccessSpy = jest.fn();
|
||||
const fakeNewVertexData: ViewModels.NewVertexData = {
|
||||
label: "",
|
||||
properties: [],
|
||||
};
|
||||
|
||||
const result = onSubmitSpy(fakeNewVertexData, onErrorSpy, onSuccessSpy);
|
||||
|
||||
const newWrapper = shallow(
|
||||
<NewVertexPanel
|
||||
explorer={fakeExplorer}
|
||||
partitionKeyPropertyProp={undefined}
|
||||
openNotificationConsole={(): void => undefined}
|
||||
onSubmit={onSubmitSpy}
|
||||
/>
|
||||
);
|
||||
//eslint-disable-next-line
|
||||
newWrapper.find("form").simulate("submit", { preventDefault: () => {} });
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(onSubmitSpy).toHaveBeenCalledWith(fakeNewVertexData, onErrorSpy, onSuccessSpy);
|
||||
});
|
||||
});
|
||||
74
src/Explorer/Panes/NewVertexPanel/NewVertexPanel.tsx
Normal file
74
src/Explorer/Panes/NewVertexPanel/NewVertexPanel.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useBoolean } from "@uifabric/react-hooks";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NewVertexComponent } from "../../Graph/NewVertexComponent/NewVertexComponent";
|
||||
import { PanelFooterComponent } from "../PanelFooterComponent";
|
||||
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||
import { PanelLoadingScreen } from "../PanelLoadingScreen";
|
||||
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>("");
|
||||
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
|
||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||
const buttonLabel = "OK";
|
||||
|
||||
const submit = (event: React.MouseEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setErrorMessage(undefined);
|
||||
setShowErrorDetails(false);
|
||||
if (onSubmit !== undefined) {
|
||||
setLoadingTrue();
|
||||
onSubmit(newVertexDataValue, onError, onSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
const onError = (errorMsg: string) => {
|
||||
setErrorMessage(errorMsg);
|
||||
setShowErrorDetails(true);
|
||||
setLoadingFalse();
|
||||
};
|
||||
|
||||
const onSuccess = () => {
|
||||
setLoadingFalse();
|
||||
explorer.closeSidePanel();
|
||||
};
|
||||
|
||||
const onChange = (newVertexData: ViewModels.NewVertexData) => {
|
||||
newVertexDataValue = newVertexData;
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="panelFormWrapper" onSubmit={(event: React.MouseEvent<HTMLFormElement>) => submit(event)}>
|
||||
{errorMessage && (
|
||||
<PanelInfoErrorComponent
|
||||
message={errorMessage}
|
||||
messageType="error"
|
||||
showErrorDetails={showErrorDetails}
|
||||
openNotificationConsole={openNotificationConsole}
|
||||
/>
|
||||
)}
|
||||
<div className="panelMainContent">
|
||||
<NewVertexComponent
|
||||
newVertexDataProp={newVertexDataValue}
|
||||
partitionKeyPropertyProp={partitionKeyPropertyProp}
|
||||
onChangeProp={onChange}
|
||||
/>
|
||||
</div>
|
||||
<PanelFooterComponent buttonLabel={buttonLabel} />
|
||||
{isLoading && <PanelLoadingScreen />}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`New Vertex Panel should render default property 1`] = `
|
||||
<form
|
||||
className="panelFormWrapper"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="panelMainContent"
|
||||
>
|
||||
<NewVertexComponent
|
||||
onChangeProp={[Function]}
|
||||
partitionKeyPropertyProp=""
|
||||
/>
|
||||
</div>
|
||||
<PanelFooterComponent
|
||||
buttonLabel="OK"
|
||||
/>
|
||||
</form>
|
||||
`;
|
||||
@@ -2,7 +2,6 @@ import AddCollectionPaneTemplate from "./AddCollectionPane.html";
|
||||
import AddDatabasePaneTemplate from "./AddDatabasePane.html";
|
||||
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
|
||||
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
|
||||
import GraphNewVertexPaneTemplate from "./GraphNewVertexPane.html";
|
||||
import GraphStylingPaneTemplate from "./GraphStylingPane.html";
|
||||
import StringInputPaneTemplate from "./StringInputPane.html";
|
||||
import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html";
|
||||
@@ -32,15 +31,6 @@ export class AddCollectionPaneComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export class GraphNewVertexPaneComponent {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: PaneComponent,
|
||||
template: GraphNewVertexPaneTemplate,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GraphStylingPaneComponent {
|
||||
constructor() {
|
||||
return {
|
||||
|
||||
@@ -187,22 +187,6 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
@@ -547,22 +531,6 @@ exports[`Settings Pane should render Default properly 1`] = `
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"newVertexPane": NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
@@ -970,22 +938,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
@@ -1330,22 +1282,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"newVertexPane": NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
|
||||
@@ -187,22 +187,6 @@ exports[`Upload Items Pane should render Default properly 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
@@ -547,22 +531,6 @@ exports[`Upload Items Pane should render Default properly 1`] = `
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"newVertexPane": NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
|
||||
@@ -188,22 +188,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
@@ -551,22 +535,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||
"isSynapseLinkUpdating": [Function],
|
||||
"isTabsContentExpanded": [Function],
|
||||
"memoryUsageInfo": [Function],
|
||||
"newVertexPane": NewVertexPane {
|
||||
"buildString": [Function],
|
||||
"container": [Circular],
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"id": "newvertexpane",
|
||||
"isExecuting": [Function],
|
||||
"isTemplateReady": [Function],
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"onSubmitCreateCallback": null,
|
||||
"partitionKeyProperty": [Function],
|
||||
"tempVertexData": [Function],
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"notebookBasePath": [Function],
|
||||
"notebookServerInfo": [Function],
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
|
||||
Reference in New Issue
Block a user