Migrate Stored Procedure tab to react (#894)

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
vaidankarswapnil 2021-06-23 22:19:57 +05:30 committed by GitHub
parent f255387ccd
commit b392bed1b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 727 additions and 439 deletions

View File

@ -146,7 +146,7 @@ src/Explorer/Tabs/MongoDocumentsTab.ts
# src/Explorer/Tabs/MongoShellTab.ts
src/Explorer/Tabs/NotebookV2Tab.ts
src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/StoredProcedureTab.ts
# src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts

View File

@ -724,45 +724,24 @@ execute-sproc-params-pane {
.results-container,
.errors-container {
padding: @MediumSpace 0px 0px @MediumSpace;
height: 100%;
.flex-display();
.flex-direction();
overflow: hidden;
.toggles {
height: @ToggleHeight;
width: @ToggleWidth;
margin-left: @MediumSpace;
&:focus {
.focus();
}
.tab {
margin-right: @MediumSpace;
}
.toggleSwitch {
.toggleSwitch();
}
.selectedToggle {
.selectedToggle();
}
.unselectedToggle {
.unselectedToggle();
}
}
.enterInputParameters {
padding: @LargeSpace @MediumSpace;
}
div[role="tabpanel"] {
height: 100%;
padding-bottom: 50px;
}
}
.errors-container {
padding-left: (2 * @MediumSpace);
padding: @MediumSpace 0px 0px @MediumSpace;
.errors-header {
font-weight: 700;
font-size: @DefaultFontSize;

View File

@ -1,89 +0,0 @@
<div class="tab-pane flexContainer stored-procedure-tab" data-bind="attr:{ id: tabId }" role="tabpanel">
<!-- Stored Procedure Tab Form - Start -->
<div class="storedTabForm flexContainer">
<div class="formTitleFirst">Stored Procedure Id</div>
<span class="formTitleTextbox">
<input
class="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
aria-label="Stored procedure id"
placeholder="Enter the new stored procedure id"
size="40"
data-bind="
textInput: id"
/>
</span>
<div class="spUdfTriggerHeader">Stored Procedure Body</div>
<editor
params="{
content: originalSprocBody,
contentType: 'javascript',
isReadOnly: false,
ariaLabel: 'Stored procedure body',
lineNumbers: 'on',
updatedContent: editorContent,
theme: _theme
}"
data-bind="attr: { id: editorId }"
></editor>
<!-- Results & Errors Content - Start-->
<div class="results-container" data-bind="visible: hasResults">
<div
class="toggles"
id="execute-storedproc-toggles"
aria-label="Successful execution of stored procedure"
data-bind="event: { keydown: onToggleKeyDown }"
tabindex="0"
>
<div class="tab">
<input type="radio" class="radio" value="result" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleResult, css:{ selectedToggle: isResultToggled(), unselectedToggle: !isResultToggled() }"
aria-label="Result"
>Result</span
>
</div>
<div class="tab">
<input type="radio" class="radio" value="logs" />
<span
class="toggleSwitch"
role="button"
tabindex="0"
data-bind="click: toggleLogs, css:{ selectedToggle: isLogsToggled(), unselectedToggle: !isLogsToggled() }"
aria-label="console.log"
>console.log</span
>
</div>
</div>
<json-editor
params="{ content: resultsData, isReadOnly: true, ariaLabel: 'Execute stored procedure result' }"
data-bind="attr: { id: executeResultsEditorId }, visible: hasResults() && isResultToggled()"
>
</json-editor>
<json-editor
params="{ content: logsData, isReadOnly: true, ariaLabel: 'Execute stored procedure logs' }"
data-bind="attr: { id: executeLogsEditorId }, visible: hasResults() && isLogsToggled()"
></json-editor>
</div>
<div class="errors-container" data-bind="visible: hasErrors">
<div class="errors-header">Errors:</div>
<div class="errorContent">
<span class="errorMessage" data-bind="text: error"></span>
<span class="errorDetailsLink">
<a
data-bind="click: $data.onErrorDetailsClick, event: { keypress: $data.onErrorDetailsKeyPress }"
aria-label="Error details link"
>More details</a
>
</span>
</div>
</div>
<!-- Results & Errors Content - End-->
</div>
</div>

View File

@ -1,287 +0,0 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import * as ko from "knockout";
import Q from "q";
import * as _ from "underscore";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import * as Constants from "../../Common/Constants";
import { createStoredProcedure } from "../../Common/dataAccess/createStoredProcedure";
import { updateStoredProcedure } from "../../Common/dataAccess/updateStoredProcedure";
import editable from "../../Common/EditableUtility";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import StoredProcedure from "../Tree/StoredProcedure";
import ScriptTabBase from "./ScriptTabBase";
import template from "./StoredProcedureTab.html";
enum ToggleState {
Result = "result",
Logs = "logs",
}
export default class StoredProcedureTab extends ScriptTabBase {
public readonly html = template;
public collection: ViewModels.Collection;
public node: StoredProcedure;
public executeResultsEditorId: string;
public executeLogsEditorId: string;
public toggleState: ko.Observable<ToggleState>;
public originalSprocBody: ViewModels.Editable<string>;
public resultsData: ko.Observable<string>;
public logsData: ko.Observable<string>;
public error: ko.Observable<string>;
public hasResults: ko.Observable<boolean>;
public hasErrors: ko.Observable<boolean>;
constructor(options: ViewModels.ScriptTabOption) {
super(options);
super.onActivate.bind(this);
this.executeResultsEditorId = `executestoredprocedureresults${this.tabId}`;
this.executeLogsEditorId = `executestoredprocedurelogs${this.tabId}`;
this.toggleState = ko.observable<ToggleState>(ToggleState.Result);
this.originalSprocBody = editable.observable<string>(this.editorContent());
this.resultsData = ko.observable<string>();
this.logsData = ko.observable<string>();
this.error = ko.observable<string>();
this.hasResults = ko.observable<boolean>(false);
this.hasErrors = ko.observable<boolean>(false);
this.error.subscribe((error: string) => {
this.hasErrors(error != null);
this.hasResults(error == null);
});
this.ariaLabel("Stored Procedure Body");
this.buildCommandBarOptions();
}
public onSaveClick = (): Promise<StoredProcedureDefinition & Resource> => {
return this._createStoredProcedure({
id: this.id(),
body: this.editorContent(),
});
};
public onDiscard = (): Q.Promise<any> => {
this.setBaselines();
const original = this.editorContent.getEditableOriginalValue();
this.originalSprocBody(original);
this.originalSprocBody.valueHasMutated(); // trigger a re-render of the editor
return Q();
};
public onUpdateClick = (): Promise<any> => {
const data = this._getResource();
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateStoredProcedure, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
});
return updateStoredProcedure(this.collection.databaseId, this.collection.id(), data)
.then(
(updatedResource) => {
this.resource(updatedResource);
this.tabTitle(updatedResource.id);
this.node.id(updatedResource.id);
this.node.body(updatedResource.body as string);
this.setBaselines();
const editorModel = this.editor() && this.editor().getModel();
editorModel && editorModel.setValue(updatedResource.body as string);
this.editorContent.setBaseline(updatedResource.body as string);
TelemetryProcessor.traceSuccess(
Action.UpdateStoredProcedure,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
},
startKey
);
},
(error: any) => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.UpdateStoredProcedure,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
}
)
.finally(() => this.isExecuting(false));
};
public onExecuteSprocsResult(result: any, logsData: any): void {
const resultData: string = this.renderObjectForEditor(_.omit(result, "scriptLogs").result, null, 4);
const scriptLogs: string = (result.scriptLogs && decodeURIComponent(result.scriptLogs)) || "";
const logs: string = this.renderObjectForEditor(scriptLogs, null, 4);
this.error(null);
this.resultsData(resultData);
this.logsData(logs);
}
public onExecuteSprocsError(error: string): void {
this.isExecutionError(true);
console.error(error);
this.error(error);
}
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
useNotificationConsole.getState().expandConsole();
return false;
};
public onErrorDetailsKeyPress = (src: any, event: KeyboardEvent): boolean => {
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
this.onErrorDetailsClick(src, null);
return false;
}
return true;
};
public toggleResult(): void {
this.toggleState(ToggleState.Result);
this.resultsData.valueHasMutated(); // needed to refresh the json-editor component
}
public toggleLogs(): void {
this.toggleState(ToggleState.Logs);
this.logsData.valueHasMutated(); // needed to refresh the json-editor component
}
public onToggleKeyDown = (source: any, event: KeyboardEvent): boolean => {
if (event.keyCode === Constants.KeyCodes.LeftArrow) {
this.toggleResult();
event.stopPropagation();
return false;
} else if (event.keyCode === Constants.KeyCodes.RightArrow) {
this.toggleLogs();
event.stopPropagation();
return false;
}
return true;
};
public isResultToggled(): boolean {
return this.toggleState() === ToggleState.Result;
}
public isLogsToggled(): boolean {
return this.toggleState() === ToggleState.Logs;
}
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.isStoredProceduresExpanded()) {
this.collection.container.selectedNode(this.collection);
} else {
this.collection.container.selectedNode(this.node);
}
}
protected buildCommandBarOptions(): void {
ko.computed(() => ko.toJSON([this.isNew, this.formIsDirty])).subscribe(() => this.updateNavbarWithTabsButtons());
super.buildCommandBarOptions();
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const label = "Execute";
return super.getTabsButtons().concat({
iconSrc: ExecuteQueryIcon,
iconAlt: label,
onCommandClick: () => {
this.collection && this.collection.container.openExecuteSprocParamsPanel(this.node);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: this.isNew() || this.formIsDirty(),
});
}
private _getResource() {
return {
id: this.id(),
body: this.editorContent(),
};
}
private _createStoredProcedure(resource: StoredProcedureDefinition): Promise<StoredProcedureDefinition & Resource> {
this.isExecutionError(false);
this.isExecuting(true);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateStoredProcedure, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
});
return createStoredProcedure(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()
)}/sprocs/${createdResource.id}`
);
this.setBaselines();
const editorModel = this.editor() && this.editor().getModel();
editorModel && editorModel.setValue(createdResource.body as string);
this.editorContent.setBaseline(createdResource.body as string);
this.node = this.collection.createStoredProcedureNode(createdResource);
TelemetryProcessor.traceSuccess(
Action.CreateStoredProcedure,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
},
startKey
);
this.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
return createdResource;
},
(createError) => {
this.isExecutionError(true);
TelemetryProcessor.traceFailure(
Action.CreateStoredProcedure,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
error: getErrorMessage(createError),
errorStack: getErrorStack(createError),
},
startKey
);
return Promise.reject(createError);
}
)
.finally(() => this.isExecuting(false));
}
public onDelete(): Q.Promise<any> {
// TODO
return Q();
}
}

View File

@ -0,0 +1,70 @@
import React from "react";
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import Explorer from "../../Explorer";
import StoredProcedure from "../../Tree/StoredProcedure";
import ScriptTabBase from "../ScriptTabBase";
import StoredProcedureTabComponent, {
IStoredProcTabComponentProps,
IStorProcTabComponentAccessor,
} from "./StoredProcedureTabComponent";
export interface IStoredProcTabProps {
container: Explorer;
collection: ViewModels.Collection;
}
export class NewStoredProcedureTab extends ScriptTabBase {
public queryText: string;
public currentQuery: string;
public partitionKey: DataModels.PartitionKey;
public iStoredProcTabComponentProps: IStoredProcTabComponentProps;
public iStoreProcAccessor: IStorProcTabComponentAccessor;
public node: StoredProcedure;
public onSaveClick: () => void;
public onUpdateClick: () => Promise<void>;
constructor(options: ViewModels.ScriptTabOption, private props: IStoredProcTabProps) {
super(options);
this.partitionKey = options.partitionKey;
this.iStoredProcTabComponentProps = {
resource: options.resource,
isNew: options.isNew,
tabKind: options.tabKind,
title: options.title,
tabPath: options.tabPath,
collectionBase: options.collection,
node: options.node,
hasLocation: options.hashLocation,
scriptTabBaseInstance: this,
collection: props.collection,
iStorProcTabComponentAccessor: (instance: IStorProcTabComponentAccessor) => {
this.iStoreProcAccessor = instance;
},
container: props.container,
};
}
public render(): JSX.Element {
return <StoredProcedureTabComponent {...this.iStoredProcTabComponentProps} />;
}
public onTabClick(): void {
this.manager?.activateTab(this);
this.iStoreProcAccessor.onTabClickEvent();
}
public onCloseTabButtonClick(): void {
this.manager?.closeTab(this);
}
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
this.iStoreProcAccessor.onExecuteSprocsResultEvent(result);
}
public onExecuteSprocsError(error: string): void {
this.iStoreProcAccessor.onExecuteSprocsErrorEvent(error);
}
}

View File

@ -0,0 +1,604 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { Pivot, PivotItem } from "@fluentui/react";
import React from "react";
import DiscardIcon from "../../../../images/discard.svg";
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
import * as Constants from "../../../Common/Constants";
import { NormalizedEventKey } from "../../../Common/Constants";
import { createStoredProcedure } from "../../../Common/dataAccess/createStoredProcedure";
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
import * as ViewModels from "../../../Contracts/ViewModels";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import StoredProcedure from "../../Tree/StoredProcedure";
import ScriptTabBase from "../ScriptTabBase";
export interface IStorProcTabComponentAccessor {
onExecuteSprocsResultEvent: (result: ExecuteSprocResult) => void;
onExecuteSprocsErrorEvent: (error: string) => void;
onTabClickEvent: () => void;
}
export interface Button {
visible: boolean;
enabled: boolean;
isSelected?: boolean;
}
interface IStoredProcTabComponentStates {
hasResults: boolean;
hasErrors: boolean;
error: string;
resultData: string;
logsData: string;
originalSprocBody: string;
initialEditorContent: string;
sProcEditorContent: string;
id: string;
executeButton: Button;
saveButton: Button;
updateButton: Button;
discardButton: Button;
}
export interface IStoredProcTabComponentProps {
resource: StoredProcedureDefinition;
isNew: boolean;
tabKind: ViewModels.CollectionTabKind;
title: string;
tabPath: string;
collectionBase: ViewModels.CollectionBase;
//eslint-disable-next-line
node?: any;
hasLocation: string;
scriptTabBaseInstance: ScriptTabBase;
collection: ViewModels.Collection;
iStorProcTabComponentAccessor: (instance: IStorProcTabComponentAccessor) => void;
container: Explorer;
}
export default class StoredProcedureTabComponent extends React.Component<
IStoredProcTabComponentProps,
IStoredProcTabComponentStates
> {
public node: StoredProcedure;
public executeResultsEditorId: string;
public executeLogsEditorId: string;
public collection: ViewModels.Collection;
constructor(
public storedProcTabCompProps: IStoredProcTabComponentProps,
private storedProcTabCompStates: IStoredProcTabComponentStates
) {
super(storedProcTabCompProps);
this.state = {
error: "",
hasErrors: false,
hasResults: false,
resultData: "",
logsData: "",
originalSprocBody: this.props.resource.body.toString(),
initialEditorContent: this.props.resource.body.toString(),
sProcEditorContent: this.props.resource.body.toString(),
id: this.props.resource.id,
executeButton: {
enabled: !this.props.scriptTabBaseInstance.isNew(),
visible: true,
},
saveButton: {
enabled: (() => {
if (!this.props.scriptTabBaseInstance.formIsValid()) {
return false;
}
if (!this.props.scriptTabBaseInstance.formIsDirty()) {
return false;
}
return true;
})(),
visible: this.props.scriptTabBaseInstance.isNew(),
},
updateButton: {
enabled: (() => {
if (!this.props.scriptTabBaseInstance.formIsValid()) {
return false;
}
if (!this.props.scriptTabBaseInstance.formIsDirty()) {
return false;
}
return true;
})(),
visible: !this.props.scriptTabBaseInstance.isNew(),
},
discardButton: {
enabled: (() => {
if (!this.props.scriptTabBaseInstance.formIsValid()) {
return false;
}
if (!this.props.scriptTabBaseInstance.formIsDirty()) {
return false;
}
return true;
})(),
visible: true,
},
};
this.collection = this.props.collection;
this.executeResultsEditorId = `executestoredprocedureresults${this.props.scriptTabBaseInstance.tabId}`;
this.executeLogsEditorId = `executestoredprocedurelogs${this.props.scriptTabBaseInstance.tabId}`;
this.props.scriptTabBaseInstance.ariaLabel("Stored Procedure Body");
this.props.iStorProcTabComponentAccessor({
onExecuteSprocsResultEvent: this.onExecuteSprocsResult.bind(this),
onExecuteSprocsErrorEvent: this.onExecuteSprocsError.bind(this),
onTabClickEvent: this.onTabClick.bind(this),
});
this.node = this.props.node;
this.buildCommandBarOptions();
}
public onTabClick(): void {
if (this.props.container.tabsManager.openedTabs().length > 0) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
}
public onSaveClick = (): Promise<StoredProcedureDefinition & Resource> => {
return this._createStoredProcedure({
id: this.state.id,
body: this.state.sProcEditorContent,
});
};
public onDiscard = (): Promise<unknown> => {
const onDiscardPromise = new Promise(() => {
this.props.scriptTabBaseInstance.setBaselines();
const original = this.props.scriptTabBaseInstance.editorContent.getEditableOriginalValue();
if (this.state.updateButton.visible) {
this.setState({
updateButton: {
enabled: false,
visible: true,
},
sProcEditorContent: original,
discardButton: {
enabled: false,
visible: true,
},
executeButton: {
enabled: true,
visible: true,
},
});
} else {
this.setState({
saveButton: {
enabled: false,
visible: true,
},
sProcEditorContent: original,
discardButton: {
enabled: false,
visible: true,
},
executeButton: {
enabled: false,
visible: true,
},
id: "",
});
}
});
setTimeout(() => {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}, 100);
return onDiscardPromise;
};
public onUpdateClick = (): Promise<void> => {
const data = this._getResource();
this.props.scriptTabBaseInstance.isExecutionError(false);
this.props.scriptTabBaseInstance.isExecuting(true);
return updateStoredProcedure(
this.props.scriptTabBaseInstance.collection.databaseId,
this.props.scriptTabBaseInstance.collection.id(),
data
)
.then(
(updatedResource) => {
this.props.scriptTabBaseInstance.resource(updatedResource);
this.props.scriptTabBaseInstance.tabTitle(updatedResource.id);
this.node.id(updatedResource.id);
this.node.body(updatedResource.body as string);
this.props.scriptTabBaseInstance.setBaselines();
const editorModel =
this.props.scriptTabBaseInstance.editor() && this.props.scriptTabBaseInstance.editor().getModel();
editorModel && editorModel.setValue(updatedResource.body as string);
this.props.scriptTabBaseInstance.editorContent.setBaseline(updatedResource.body as string);
this.setState({
discardButton: {
enabled: false,
visible: true,
},
updateButton: {
enabled: false,
visible: true,
},
executeButton: {
enabled: true,
visible: true,
},
});
useCommandBar.getState().setContextButtons(this.getTabsButtons());
},
() => {
this.props.scriptTabBaseInstance.isExecutionError(true);
}
)
.finally(() => this.props.scriptTabBaseInstance.isExecuting(false));
};
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
const resultData: string = this.props.scriptTabBaseInstance.renderObjectForEditor(result.result, undefined, 4);
const scriptLogs: string = (result.scriptLogs && decodeURIComponent(result.scriptLogs)) || "";
const logs: string = this.props.scriptTabBaseInstance.renderObjectForEditor(scriptLogs, undefined, 4);
this.setState({
hasResults: false,
});
setTimeout(() => {
this.setState({
error: undefined,
resultData: resultData,
logsData: logs,
hasResults: resultData ? true : false,
hasErrors: false,
});
}, 100);
}
public onExecuteSprocsError(error: string): void {
this.props.scriptTabBaseInstance.isExecutionError(true);
console.error(error);
this.setState({
error: error,
hasErrors: true,
hasResults: false,
});
}
public onErrorDetailsClick = (): boolean => {
useNotificationConsole.getState().expandConsole();
return false;
};
public onErrorDetailsKeyPress = (event: React.KeyboardEvent<HTMLAnchorElement>): boolean => {
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
this.onErrorDetailsClick();
return false;
}
return true;
};
protected updateSelectedNode(): void {
if (this.props.collectionBase === undefined) {
return;
}
const database: ViewModels.Database = this.props.collectionBase.getDatabase();
if (!database.isDatabaseExpanded()) {
this.props.collectionBase.container.selectedNode(database);
} else if (!this.props.collectionBase.isCollectionExpanded() || !this.collection.isStoredProceduresExpanded()) {
this.props.collectionBase.container.selectedNode(this.props.collectionBase);
} else {
this.props.collectionBase.container.selectedNode(this.node);
}
}
protected buildCommandBarOptions(): void {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}
protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [];
const label = "Save";
if (this.state.saveButton.visible) {
buttons.push({
iconSrc: SaveIcon,
iconAlt: label,
onCommandClick: this.onSaveClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.state.saveButton.enabled,
});
}
if (this.state.updateButton.visible) {
const label = "Update";
buttons.push({
iconSrc: SaveIcon,
iconAlt: label,
onCommandClick: this.onUpdateClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.state.updateButton.enabled,
});
}
if (this.state.discardButton.visible) {
const label = "Discard";
buttons.push({
iconSrc: DiscardIcon,
iconAlt: label,
onCommandClick: this.onDiscard,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.state.discardButton.enabled,
});
}
if (this.state.executeButton.visible) {
const label = "Execute";
buttons.push({
iconSrc: ExecuteQueryIcon,
iconAlt: label,
onCommandClick: () => {
this.collection.container.openExecuteSprocParamsPanel(this.node);
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.state.executeButton.enabled,
});
}
return buttons;
}
private _getResource() {
return {
id: this.state.id,
body: this.state.sProcEditorContent,
};
}
private _createStoredProcedure(resource: StoredProcedureDefinition): Promise<StoredProcedureDefinition & Resource> {
this.props.scriptTabBaseInstance.isExecutionError(false);
this.props.scriptTabBaseInstance.isExecuting(true);
return createStoredProcedure(this.props.collectionBase.databaseId, this.props.collectionBase.id(), resource)
.then(
(createdResource) => {
this.props.scriptTabBaseInstance.tabTitle(createdResource.id);
this.props.scriptTabBaseInstance.isNew(false);
this.props.scriptTabBaseInstance.resource(createdResource);
this.props.scriptTabBaseInstance.hashLocation(
`${Constants.HashRoutePrefixes.collectionsWithIds(
this.props.collectionBase.databaseId,
this.props.collectionBase.id()
)}/sprocs/${createdResource.id}`
);
this.props.scriptTabBaseInstance.setBaselines();
const editorModel =
this.props.scriptTabBaseInstance.editor() && this.props.scriptTabBaseInstance.editor().getModel();
editorModel && editorModel.setValue(createdResource.body as string);
this.props.scriptTabBaseInstance.editorContent.setBaseline(createdResource.body as string);
this.node = this.collection.createStoredProcedureNode(createdResource);
this.props.container.tabsManager.openedTabs()[
this.props.container.tabsManager.openedTabs().length - 1
].node = this.node;
this.props.scriptTabBaseInstance.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
this.setState({
executeButton: {
enabled: false,
visible: true,
},
});
setTimeout(() => {
this.setState({
executeButton: {
enabled: true,
visible: true,
},
updateButton: {
enabled: false,
visible: true,
},
saveButton: {
enabled: false,
visible: false,
},
discardButton: {
enabled: false,
visible: true,
},
sProcEditorContent: this.state.sProcEditorContent,
});
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}, 100);
return createdResource;
},
(createError) => {
this.props.scriptTabBaseInstance.isExecutionError(true);
return Promise.reject(createError);
}
)
.finally(() => this.props.scriptTabBaseInstance.isExecuting(false));
}
public onDelete(): Promise<unknown> {
const isDeleted = false;
const onDeletePromise = new Promise((resolve) => {
resolve(isDeleted);
});
return onDeletePromise;
}
public handleIdOnChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (this.state.saveButton.visible) {
this.setState({
id: event.target.value,
saveButton: {
enabled: true,
visible: this.props.scriptTabBaseInstance.isNew(),
},
discardButton: {
enabled: true,
visible: true,
},
});
}
setTimeout(() => {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}, 1000);
}
public onChangeContent(newConent: string): void {
if (this.state.updateButton.visible) {
this.setState({
updateButton: {
enabled: true,
visible: true,
},
discardButton: {
enabled: true,
visible: true,
},
executeButton: {
enabled: false,
visible: true,
},
sProcEditorContent: newConent,
});
} else {
this.setState({
saveButton: {
enabled: false,
visible: this.props.scriptTabBaseInstance.isNew(),
},
executeButton: {
enabled: false,
visible: true,
},
discardButton: {
enabled: true,
visible: true,
},
sProcEditorContent: newConent,
});
}
setTimeout(() => {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}, 100);
}
render(): JSX.Element {
return (
<div className="tab-pane flexContainer stored-procedure-tab" role="tabpanel">
<div className="storedTabForm flexContainer">
<div className="formTitleFirst">Stored Procedure Id</div>
<span className="formTitleTextbox">
<input
className="formTree"
type="text"
required
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
aria-label="Stored procedure id"
placeholder="Enter the new stored procedure id"
size={40}
value={this.state.id}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => this.handleIdOnChange(event)}
/>
</span>
<div className="spUdfTriggerHeader">Stored Procedure Body</div>
<EditorReact
language={"javascript"}
content={this.state.sProcEditorContent}
isReadOnly={false}
ariaLabel={"Stored procedure body"}
lineNumbers={"on"}
theme={"_theme"}
onContentChanged={(newContent: string) => this.onChangeContent(newContent)}
/>
{this.state.hasResults && (
<div className="results-container">
<Pivot aria-label="Successful execution of stored procedure" style={{ height: "100%" }}>
<PivotItem
headerText="Result"
headerButtonProps={{
"data-order": 1,
"data-title": "Result",
}}
style={{ height: "100%" }}
>
<EditorReact
language={"javascript"}
content={this.state.resultData}
isReadOnly={true}
ariaLabel={"Execute stored procedure result"}
/>
</PivotItem>
<PivotItem
headerText="console.log"
headerButtonProps={{
"data-order": 2,
"data-title": "console.log",
}}
style={{ height: "100%" }}
>
<EditorReact
language={"javascript"}
content={this.state.logsData}
isReadOnly={true}
ariaLabel={"Execute stored procedure logs"}
/>
</PivotItem>
</Pivot>
</div>
)}
{this.state.hasErrors && (
<div className="errors-container">
<div className="errors-header">Errors:</div>
<div className="errorContent">
<span className="errorMessage">{this.state.error}</span>
<span className="errorDetailsLink">
<a
aria-label="Error details link"
onClick={() => this.onErrorDetailsClick()}
onKeyPress={(event: React.KeyboardEvent<HTMLAnchorElement>) => this.onErrorDetailsKeyPress(event)}
>
More details
</a>
</span>
</div>
</div>
)}
</div>
</div>
);
}
}

View File

@ -3,13 +3,13 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import Explorer from "../Explorer";
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
import { getErrorMessage } from "../Tables/Utilities";
import { NewStoredProcedureTab } from "../Tabs/StoredProcedureTab/StoredProcedureTab";
import TabsBase from "../Tabs/TabsBase";
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
@ -67,16 +67,22 @@ export default class StoredProcedure {
body: sampleStoredProcedureBody,
};
const storedProcedureTab: StoredProcedureTab = new StoredProcedureTab({
resource: storedProcedure,
isNew: true,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: `New Stored Procedure ${id}`,
tabPath: `${source.databaseId}>${source.id()}>New Stored Procedure ${id}`,
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
});
const storedProcedureTab: NewStoredProcedureTab = new NewStoredProcedureTab(
{
resource: storedProcedure,
isNew: true,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: `New Stored Procedure ${id}`,
tabPath: `${source.databaseId}>${source.id()}>New Stored Procedure ${id}`,
collection: source,
node: source,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
},
{
collection: source,
container: source.container,
}
);
source.container.tabsManager.activateNewTab(storedProcedureTab);
}
@ -93,11 +99,11 @@ export default class StoredProcedure {
public open = () => {
this.select();
const storedProcedureTabs: StoredProcedureTab[] = this.container.tabsManager.getTabs(
const storedProcedureTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
) as StoredProcedureTab[];
let storedProcedureTab: StoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
) as NewStoredProcedureTab[];
let storedProcedureTab: NewStoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
if (storedProcedureTab) {
this.container.tabsManager.activateTab(storedProcedureTab);
@ -109,24 +115,29 @@ export default class StoredProcedure {
body: this.body(),
};
storedProcedureTab = new StoredProcedureTab({
resource: storedProcedureData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: storedProcedureData.id,
tabPath: `${this.collection.databaseId}>${this.collection.id()}>${storedProcedureData.id}`,
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/sprocs/${this.id()}`,
});
storedProcedureTab = new NewStoredProcedureTab(
{
resource: storedProcedureData,
isNew: false,
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
title: storedProcedureData.id,
tabPath: `${this.collection.databaseId}>${this.collection.id()}>${storedProcedureData.id}`,
collection: this.collection,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
this.collection.databaseId,
this.collection.id()
)}/sprocs/${this.id()}`,
},
{
collection: this.collection,
container: this.container,
}
);
this.container.tabsManager.activateNewTab(storedProcedureTab);
}
};
public delete() {
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
return;
@ -142,19 +153,19 @@ export default class StoredProcedure {
}
public execute(params: string[], partitionKeyValue?: string): void {
const sprocTabs = this.container.tabsManager.getTabs(
const sprocTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
) as StoredProcedureTab[];
const sprocTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
) as NewStoredProcedureTab[];
const sprocTab: NewStoredProcedureTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
sprocTab.isExecuting(true);
this.container &&
executeStoredProcedure(this.collection, this, partitionKeyValue, params)
.then(
(result: any) => {
sprocTab.onExecuteSprocsResult(result, result.scriptLogs);
(result) => {
sprocTab.onExecuteSprocsResult(result);
},
(error: any) => {
(error) => {
sprocTab.onExecuteSprocsError(getErrorMessage(error));
}
)