mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-24 22:46:40 +00:00
Migrate userDefinedFunctionTab to React (#860)
This commit is contained in:
parent
1d449e5b52
commit
05f59307c2
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
41
src/Explorer/Tabs/UserDefinedFunctionTab.tsx
Normal file
41
src/Explorer/Tabs/UserDefinedFunctionTab.tsx
Normal 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)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
306
src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx
Normal file
306
src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user