Migrate String Input Pane to React (#678)

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
Hardikkumar Nai
2021-04-26 06:52:46 +05:30
committed by GitHub
parent 67062c18aa
commit 71e7ad4547
16 changed files with 4776 additions and 460 deletions

View File

@@ -3,7 +3,6 @@ import AddDatabasePaneTemplate from "./AddDatabasePane.html";
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
import GraphStylingPaneTemplate from "./GraphStylingPane.html";
import StringInputPaneTemplate from "./StringInputPane.html";
import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html";
import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html";
@@ -66,15 +65,6 @@ export class CassandraAddCollectionPaneComponent {
}
}
export class StringInputPaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: StringInputPaneTemplate,
};
}
}
export class GitHubReposPaneComponent {
constructor() {
return {

View File

@@ -200,20 +200,6 @@ exports[`Settings Pane should render Default properly 1`] = `
"userTableQuery": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
],
"_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function],
@@ -524,20 +510,6 @@ exports[`Settings Pane should render Default properly 1`] = `
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],
@@ -883,20 +855,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
"userTableQuery": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
],
"_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function],
@@ -1207,20 +1165,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],

View File

@@ -1,66 +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" id="stringInputPane">
<!-- String Input form -- Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- String Input header - Start -->
<div class="firstdivbg headerline">
<span role="heading" aria-level="2" data-bind="text: title"></span>
<div class="closeImg" role="button" aria-label="Close pane" tabindex="0" data-bind="click: cancel"></div>
</div>
<!-- String Input header - End -->
<!-- String Input errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
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"
>More details</a
>
</span>
</div>
</div>
<!-- String Input errors - End -->
<!-- String Input inputs - Start -->
<div class="paneMainContent">
<div>
<p data-bind="text: inputLabel"></p>
<p>
<input
type="text"
name="collectionIdConfirmation"
required
class="collid"
data-bind="textInput: stringInput, hasFocus: firstFieldHasFocus"
aria-label="inputLabel"
/>
</p>
</div>
</div>
<div class="paneFooter">
<div class="leftpanel-okbut">
<input type="submit" data-bind="attr: { value: submitButtonLabel }" class="btncreatecoll1" />
</div>
</div>
<!-- String Input inputs - End -->
</form>
</div>
<!-- String Input form - Start -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -1,99 +0,0 @@
import * as ko from "knockout";
import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { ContextualPaneBase } from "./ContextualPaneBase";
export interface StringInputPaneOpenOptions {
paneTitle: string;
inputLabel: string;
errorMessage: string;
inProgressMessage: string;
successMessage: string;
onSubmit: (input: string) => Promise<any>;
submitButtonLabel: string;
defaultInput?: string;
}
/**
* Generic pane to get a single string input from user
*/
export class StringInputPane extends ContextualPaneBase {
private openOptions: StringInputPaneOpenOptions;
private submitButtonLabel: ko.Observable<string>;
private inputLabel: ko.Observable<string>;
private stringInput: ko.Observable<string>;
private paneDeferred: Q.Deferred<any>;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.resetData();
this.inputLabel = ko.observable("");
this.submitButtonLabel = ko.observable("Load");
this.stringInput = ko.observable();
}
public submit() {
this.formErrors("");
this.formErrorsDetails("");
const clearInProgressMessage = logConsoleProgress(`${this.openOptions.inProgressMessage} ${this.stringInput()}`);
this.isExecuting(true);
this.openOptions
.onSubmit(this.stringInput())
.then(
(value: any) => {
logConsoleInfo(`${this.openOptions.successMessage}: ${this.stringInput()}`);
this.close();
this.paneDeferred.resolve(value);
},
(reason) => {
let error = reason;
if (reason instanceof Error) {
error = reason.message;
} else if (typeof reason === "object") {
error = JSON.stringify(reason);
}
// If it's an AjaxError (AjaxObservable), add more error
if (reason.response && reason.response.message) {
error += `. ${reason.response.message}`;
}
this.formErrors(this.openOptions.errorMessage);
this.formErrorsDetails(`${this.openOptions.errorMessage}: ${error}`);
logConsoleError(`${this.openOptions.errorMessage} ${this.stringInput()}: ${error}`);
this.paneDeferred.reject(error);
}
)
.finally(() => {
this.isExecuting(false);
clearInProgressMessage();
});
}
public close() {
super.close();
this.resetData();
this.resetFileInput();
}
public openWithOptions<T>(options: StringInputPaneOpenOptions): Q.Promise<T> {
this.openOptions = options;
this.title(this.openOptions.paneTitle);
if (this.openOptions.submitButtonLabel) {
this.submitButtonLabel(this.openOptions.submitButtonLabel);
}
this.inputLabel(this.openOptions.inputLabel);
this.stringInput(this.openOptions.defaultInput);
super.open();
this.paneDeferred = Q.defer<T>();
return this.paneDeferred.promise;
}
private resetFileInput(): void {
this.stringInput("");
}
}

View File

@@ -0,0 +1,28 @@
import { mount } from "enzyme";
import React from "react";
import Explorer from "../../Explorer";
import { StringInputPane } from "./StringInputPane";
const props = {
explorer: new Explorer(),
closePanel: (): void => undefined,
errorMessage: "Could not create directory ",
inProgressMessage: "Creating directory ",
successMessage: "Created directory ",
inputLabel: "Enter new directory name",
paneTitle: "Create new directory",
submitButtonLabel: "Create",
defaultInput: "",
onSubmit: jest.fn(),
notebookFile: {
name: "Untitled1123.ipynb",
path: "notebooks/Untitled1123.ipynb",
type: 0,
timestamp: 1618452275805,
},
};
describe("StringInput Pane", () => {
it("should render Create new directory properly", () => {
const wrapper = mount(<StringInputPane {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,122 @@
import { TextField } from "office-ui-fabric-react";
import React, { FormEvent, FunctionComponent, useState } from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
import {
GenericRightPaneComponent,
GenericRightPaneProps,
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
export interface StringInputPanelProps {
explorer: Explorer;
closePanel: () => void;
errorMessage: string;
inProgressMessage: string;
successMessage: string;
inputLabel: string;
paneTitle: string;
submitButtonLabel: string;
defaultInput: string;
onSubmit: (notebookFile: NotebookContentItem, input: string) => Promise<NotebookContentItem>;
notebookFile: NotebookContentItem;
}
export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
explorer: container,
closePanel,
errorMessage,
inProgressMessage,
successMessage,
inputLabel,
paneTitle,
submitButtonLabel,
defaultInput,
onSubmit,
notebookFile,
}: StringInputPanelProps): JSX.Element => {
const [stringInput, setStringInput] = useState<string>(defaultInput);
const [formErrors, setFormErrors] = useState<string>("");
const [formErrorsDetails, setFormErrorsDetails] = useState<string>("");
const [isExecuting, setIsExecuting] = useState<boolean>(false);
const submit = async (): Promise<void> => {
if (stringInput === "") {
const errorMessage = "Please " + inputLabel;
setFormErrors(errorMessage);
logConsoleError("Error while " + paneTitle + " : " + errorMessage);
return;
} else {
setFormErrors("");
setFormErrorsDetails("");
}
const clearMessage = logConsoleProgress(`${inProgressMessage} ${stringInput}`);
try {
const newNotebookFile: NotebookContentItem = await onSubmit(notebookFile, stringInput);
logConsoleInfo(`${successMessage}: ${stringInput}`);
const originalPath = notebookFile.path;
const notebookTabs = container.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
);
notebookTabs.forEach((tab) => {
tab.tabTitle(newNotebookFile.name);
tab.tabPath(newNotebookFile.path);
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
closePanel();
} catch (reason) {
let error = reason;
if (reason instanceof Error) {
error = reason.message;
} else if (typeof reason === "object") {
error = JSON.stringify(reason);
}
// If it's an AjaxError (AjaxObservable), add more error
if (reason?.response?.message) {
error += `. ${reason.response.message}`;
}
setFormErrors(errorMessage);
setFormErrorsDetails(`${errorMessage}: ${error}`);
logConsoleError(`${errorMessage} ${stringInput}: ${error}`);
} finally {
setIsExecuting(false);
clearMessage();
}
};
const genericPaneProps: GenericRightPaneProps = {
container: container,
formError: formErrors,
formErrorDetail: formErrorsDetails,
id: "stringInputPane",
isExecuting: isExecuting,
title: paneTitle,
submitButtonText: submitButtonLabel,
onClose: closePanel,
onSubmit: submit,
};
return (
<GenericRightPaneComponent {...genericPaneProps}>
<div className="paneMainContent">
<TextField
label={inputLabel}
name="collectionIdConfirmation"
value={stringInput}
autoFocus
required
onChange={(event: FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) =>
setStringInput(newValue)
}
aria-label={inputLabel}
/>
</div>
</GenericRightPaneComponent>
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -200,20 +200,6 @@ exports[`Upload Items Pane should render Default properly 1`] = `
"userTableQuery": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
],
"_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function],
@@ -524,20 +510,6 @@ exports[`Upload Items Pane should render Default properly 1`] = `
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],

View File

@@ -201,20 +201,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"userTableQuery": [Function],
"visible": [Function],
},
StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
],
"_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function],
@@ -529,20 +515,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"onResizeStop": [Function],
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
"container": [Circular],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "stringinputpane",
"inputLabel": [Function],
"isExecuting": [Function],
"isTemplateReady": [Function],
"stringInput": [Function],
"submitButtonLabel": [Function],
"title": [Function],
"visible": [Function],
},
"tabsManager": TabsManager {
"activeTab": [Function],
"openedTabs": [Function],