Migrate userDefinedFunctionTab to React (#860)

This commit is contained in:
Sunil Kumar Yadav 2021-06-17 05:39:22 +05:30 committed by GitHub
parent 1d449e5b52
commit 05f59307c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 347 additions and 192 deletions

View File

@ -1,30 +0,0 @@
<div class="tab-pane flexContainer" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- User Defined Function Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">User Defined Function Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
aria-label="User defined function id"
placeholder="Enter the new user defined function id"
size="40"
data-bind="
textInput: id"
/>
</span>
<div class="spUdfTriggerHeader">User Defined Function Body</div>
<div
class="storedUdfTriggerEditor"
data-bind="
setTemplateReady: true,
attr: {
id: editorId
}"
></div>
</div>
<!-- User Defined Function Tab Form - End -->
</div>

View File

@ -1,162 +0,0 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import * as Constants from "../../Common/Constants";
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import UserDefinedFunction from "../Tree/UserDefinedFunction";
import ScriptTabBase from "./ScriptTabBase";
import template from "./UserDefinedFunctionTab.html";
export default class UserDefinedFunctionTab extends ScriptTabBase {
public readonly html = template;
public collection: ViewModels.Collection;
public node: UserDefinedFunction;
constructor(options: ViewModels.ScriptTabOption) {
super(options);
this.ariaLabel("User Defined Function Body");
super.onActivate.bind(this);
super.buildCommandBarOptions.bind(this);
super.buildCommandBarOptions();
}
public onSaveClick = (): Promise<UserDefinedFunctionDefinition & Resource> => {
const data = this._getResource();
return this._createUserDefinedFunction(data);
};
public onUpdateClick = (): Promise<any> => {
const data = this._getResource();
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateUDF, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
});
return updateUserDefinedFunction(this.collection.databaseId, this.collection.id(), data)
.then(
(createdResource) => {
this.resource(createdResource);
this.tabTitle(createdResource.id);
this.node.id(createdResource.id);
this.node.body(createdResource.body as string);
TelemetryProcessor.traceSuccess(
Action.UpdateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
},
startKey
);
this.setBaselines();
const editorModel = this.editor().getModel();
editorModel.setValue(createdResource.body as string);
this.editorContent.setBaseline(createdResource.body as string);
},
(createError: any) => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.UpdateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError),
},
startKey
);
}
)
.finally(() => this.isExecuting(false));
};
protected updateSelectedNode(): void {
if (this.collection == null) {
return;
}
const database: ViewModels.Database = this.collection.getDatabase();
if (!database.isDatabaseExpanded()) {
this.collection.container.selectedNode(database);
} else if (!this.collection.isCollectionExpanded() || !this.collection.isUserDefinedFunctionsExpanded()) {
this.collection.container.selectedNode(this.collection);
} else {
this.collection.container.selectedNode(this.node);
}
}
private _createUserDefinedFunction(
resource: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateUDF, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
});
return createUserDefinedFunction(this.collection.databaseId, this.collection.id(), resource)
.then(
(createdResource) => {
this.tabTitle(createdResource.id);
this.isNew(false);
this.resource(createdResource);
this.hashLocation(
`${Constants.HashRoutePrefixes.collectionsWithIds(this.collection.databaseId, this.collection.id())}/udfs/${
createdResource.id
}`
);
this.setBaselines();
const editorModel = this.editor().getModel();
editorModel.setValue(createdResource.body as string);
this.editorContent.setBaseline(createdResource.body as string);
this.node = this.collection.createUserDefinedFunctionNode(createdResource);
TelemetryProcessor.traceSuccess(
Action.CreateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
},
startKey
);
this.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
return createdResource;
},
(createError: any) => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.CreateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError),
},
startKey
);
return Promise.reject(createError);
}
)
.finally(() => this.isExecuting(false));
}
private _getResource() {
const resource = {
_rid: this.resource()._rid,
_self: this.resource()._self,
id: this.id(),
body: this.editorContent(),
};
return resource;
}
}

View File

@ -0,0 +1,41 @@
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import React from "react";
import * as ViewModels from "../../Contracts/ViewModels";
import UserDefinedFunction from "../Tree/UserDefinedFunction";
import ScriptTabBase from "./ScriptTabBase";
import UserDefinedFunctionTabContent from "./UserDefinedFunctionTabContent";
export default class UserDefinedFunctionTab extends ScriptTabBase {
public onSaveClick: () => Promise<void>;
public onUpdateClick: () => Promise<void>;
public collection: ViewModels.Collection;
public node: UserDefinedFunction;
constructor(options: ViewModels.ScriptTabOption) {
super(options);
this.ariaLabel("User Defined Function Body");
super.onActivate.bind(this);
super.buildCommandBarOptions.bind(this);
super.buildCommandBarOptions();
}
addNodeInCollection(createdResource: Resource & UserDefinedFunctionDefinition): void {
this.node = this.collection.createUserDefinedFunctionNode(createdResource);
}
updateNodeInCollection(updateResource: Resource & UserDefinedFunctionDefinition): void {
this.node.id(updateResource.id);
this.node.body(updateResource.body as string);
}
render(): JSX.Element {
return (
<UserDefinedFunctionTabContent
{...this}
addNodeInCollection={(createdResource) => this.addNodeInCollection(createdResource)}
updateNodeInCollection={(updateResource: Resource & UserDefinedFunctionDefinition) =>
this.updateNodeInCollection(updateResource)
}
/>
);
}
}

View File

@ -0,0 +1,306 @@
import { UserDefinedFunctionDefinition } from "@azure/cosmos";
import { Label, TextField } from "@fluentui/react";
import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg";
import SaveIcon from "../../../images/save-cosmos.svg";
import * as Constants from "../../Common/Constants";
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../Controls/Editor/EditorReact";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import UserDefinedFunctionTab from "./UserDefinedFunctionTab";
interface IUserDefinedFunctionTabContentState {
udfId: string;
udfBody: string;
isUdfIdEditable: boolean;
}
interface Ibutton {
visible: boolean;
enabled: boolean;
}
export default class UserDefinedFunctionTabContent extends Component<
UserDefinedFunctionTab,
IUserDefinedFunctionTabContentState
> {
public saveButton: Ibutton;
public updateButton: Ibutton;
public discardButton: Ibutton;
constructor(props: UserDefinedFunctionTab) {
super(props);
this.saveButton = {
visible: props.isNew(),
enabled: false,
};
this.updateButton = {
visible: !props.isNew(),
enabled: true,
};
this.discardButton = {
visible: true,
enabled: true,
};
const { id, body } = props.resource();
this.state = {
udfId: id,
udfBody: body,
isUdfIdEditable: props.isNew() ? true : false,
};
}
private handleUdfIdChange = (
_event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
this.saveButton.enabled = this.isValidId(newValue) && this.isNotEmpty(newValue);
this.setState({ udfId: newValue });
};
private handleUdfBodyChange = (newContent: string) => {
this.setState({ udfBody: newContent });
};
protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
const label = "Save";
if (this.saveButton.visible) {
buttons.push({
...this,
setState: this.setState,
iconSrc: SaveIcon,
iconAlt: label,
onCommandClick: this.onSaveClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.saveButton.enabled,
});
}
if (this.updateButton.visible) {
const label = "Update";
buttons.push({
...this,
iconSrc: SaveIcon,
iconAlt: label,
onCommandClick: this.onUpdateClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.updateButton.enabled,
});
}
if (this.discardButton.visible) {
const label = "Discard";
buttons.push({
setState: this.setState,
...this,
iconSrc: DiscardIcon,
iconAlt: label,
onCommandClick: this.onDiscard,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.discardButton.enabled,
});
}
return buttons;
}
private async onSaveClick(): Promise<void> {
const { udfId, udfBody } = this.state;
const resource: UserDefinedFunctionDefinition = {
id: udfId,
body: udfBody,
};
this.props.isExecutionError(false);
this.props.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateUDF, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.tabTitle(),
});
try {
const createdResource = await createUserDefinedFunction(
this.props.collection.databaseId,
this.props.collection.id(),
resource
);
if (createdResource) {
this.props.tabTitle(createdResource.id);
this.props.isNew(false);
this.updateButton.visible = true;
this.saveButton.visible = false;
this.props.resource(createdResource);
this.props.hashLocation(
`${Constants.HashRoutePrefixes.collectionsWithIds(
this.props.collection.databaseId,
this.props.collection.id()
)}/udfs/${createdResource.id}`
);
this.props.addNodeInCollection(createdResource);
this.setState({ isUdfIdEditable: false });
this.props.isExecuting(false);
TelemetryProcessor.traceSuccess(
Action.CreateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.tabTitle(),
},
startKey
);
this.props.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
}
} catch (createError) {
this.props.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.CreateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError),
},
startKey
);
this.props.isExecuting(false);
return Promise.reject(createError);
}
}
private async onUpdateClick(): Promise<void> {
const { udfId, udfBody } = this.state;
const resource: UserDefinedFunctionDefinition = {
id: udfId,
body: udfBody,
};
this.props.isExecutionError(false);
this.props.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateUDF, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.tabTitle(),
});
try {
const createdResource = await updateUserDefinedFunction(
this.props.collection.databaseId,
this.props.collection.id(),
resource
);
this.props.resource(createdResource);
this.props.tabTitle(createdResource.id);
this.props.updateNodeInCollection(createdResource);
this.props.isExecuting(false);
TelemetryProcessor.traceSuccess(
Action.UpdateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.tabTitle(),
},
startKey
);
this.props.editorContent.setBaseline(createdResource.body as string);
} catch (createError) {
this.props.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.UpdateUDF,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError),
},
startKey
);
this.props.isExecuting(false);
}
}
private onDiscard(): void {
const { id, body } = this.props.resource();
this.setState({
udfId: id,
udfBody: body,
});
}
private isValidId(id: string): boolean {
if (!id) {
return false;
}
const invalidStartCharacters = /^[/?#\\]/;
if (invalidStartCharacters.test(id)) {
return false;
}
const invalidMiddleCharacters = /^.+[/?#\\]/;
if (invalidMiddleCharacters.test(id)) {
return false;
}
const invalidEndCharacters = /.*[/?#\\ ]$/;
if (invalidEndCharacters.test(id)) {
return false;
}
return true;
}
private isNotEmpty(value: string): boolean {
return !!value;
}
componentDidUpdate(_prevProps: UserDefinedFunctionTab, prevState: IUserDefinedFunctionTabContentState): void {
const { udfBody, udfId } = this.state;
if (udfId !== prevState.udfId || udfBody !== prevState.udfBody) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
}
render(): JSX.Element {
const { udfId, udfBody, isUdfIdEditable } = this.state;
return (
<div className="tab-pane flexContainer trigger-form" role="tabpanel">
<TextField
className="trigger-field"
label="User Defined Function Id"
id="entityTimeId"
autoFocus
required
readOnly={!isUdfIdEditable}
type="text"
pattern="[^/?#\\]*[^/?# \\]"
placeholder="Enter the new trigger id"
size={40}
value={udfId}
onChange={this.handleUdfIdChange}
/>
<Label className="trigger-field">User Defined Function Body</Label>
<EditorReact
language={"javascript"}
content={udfBody}
isReadOnly={false}
ariaLabel={"Graph JSON"}
onContentChanged={this.handleUdfBodyChange}
/>
</div>
);
}
}