Files
cosmos-explorer/src/Explorer/Tabs/UserDefinedFunctionTabContent.tsx
sakshigupta12feb 2c31ec2a8d Dark theme for Explorer (#2185)
* First dark theme commits for command bar

* Updated theme on sidebar

* Updated tabs, sidebar, splash screen

* settings theme changes

* Dark theme applied to Monaco editor

* Dark theme to stored procedures

* Fixed sidebar scroll

* Updated scroll issue in sidebar

* Command bar items fixed

* Fixed lint errors

* fixed lint errors

* settings side panel fixed

* Second last iteration for css

* Fixed all the issues of css

* Updated the theme icon for now on DE to change the theme from portal/DE itself

* Formatting issue resolved

* Remove CloudShellTerminalComponent changes - revert to master version

* Fixed test issue

* Fixed formatting issue

* Fix tests: update snapshots and revert xterm imports for compatibility

* Fix xterm imports in CloudShellTerminalComponent to use @xterm packages

* Fix Cloud Shell component imports for compatibility

* Update test snapshots

* Fix xterm package consistency across all CloudShell components

* Fix TypeScript compilation errors in CloudShell components and query Documents

- Standardized xterm package imports across CloudShell components to use legacy 'xterm' package
- Fixed Terminal type compatibility issues in CommonUtils.tsx
- Added type casting for enableQueryControl property in queryDocuments.ts to handle Azure Cosmos SDK interface limitations
- Applied code formatting to ensure consistency

* Update failing snapshot tests

- Updated TreeNodeComponent snapshot tests for loading states
- Updated ThroughputInputAutoPilotV3Component snapshots for number formatting changes (10,00,000 -> 1,000,000)
- All snapshot tests now pass

* Fixed test issue

* Fixed test issue

* Updated the buttons for theme

* Updated the Theme changes based on portal theme changes

* Updated review comments

* Updated the duplicate code and fixed the fabric react error

* Few places styling added and resolving few comments

* Fixed errors

* Fixed comments

* Fixed comments

* Fixed comments

* Fixed full text policy issue for mongoru accounts

* Resolved comments for class Name and few others

* Added css for homepage in ru accounts

* Final commit with all the feedback issues resolved

* Lint error resolved

* Updated the review comments and few Ui issues

* Resolved review comments and changed header bg and active state color

* Moved svg code to different file and imported

* css fixed for the hpome page boxed for ru account

* Lint errors

* Fixed boxes issue in ru accounts

* Handled the initial theme from the portal

* Updated snap

* Update snapshots for TreeNodeComponent and CreateCopyJobScreensProvider tests

* Fix duplicate DataExplorerRoot test id causing Playwright strict mode violation

* Fix locale-dependent number formatting in ThroughputInputAutoPilotV3Component

---------

Co-authored-by: Sakshi Gupta <sakshig+microsoft@microsoft.com>
Co-authored-by: Sakshi Gupta <sakshig@microsoft.com>
2025-12-16 12:21:58 +05:30

314 lines
9.6 KiB
TypeScript

import { UserDefinedFunctionDefinition } from "@azure/cosmos";
import { Label, TextField } from "@fluentui/react";
import { FluentProvider, webDarkTheme, webLightTheme } from "@fluentui/react-components";
import { KeyboardAction } from "KeyboardShortcuts";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import { useThemeStore } from "hooks/useTheme";
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 { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
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 => {
const inputElement = _event.currentTarget as HTMLInputElement;
let isValidId: boolean = true;
if (inputElement) {
isValidId = inputElement.reportValidity();
}
this.saveButton.enabled = this.isNotEmpty(newValue) && isValidId;
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,
keyboardAction: KeyboardAction.SAVE_ITEM,
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,
keyboardAction: KeyboardAction.SAVE_ITEM,
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,
keyboardAction: KeyboardAction.CANCEL_OR_DISCARD,
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.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.existingNoEdits);
}
} 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 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;
const currentTheme = useThemeStore.getState().isDarkMode ? webDarkTheme : webLightTheme;
return (
<div className="tab-pane flexContainer trigger-form" role="tabpanel">
<FluentProvider theme={currentTheme}>
<TextField
className="trigger-field"
label="User Defined Function Id"
id="entityTimeId"
autoFocus
required
readOnly={!isUdfIdEditable}
type="text"
pattern={ValidCosmosDbIdInputPattern.source}
title={ValidCosmosDbIdDescription}
placeholder="Enter the new user defined function id"
size={40}
value={udfId}
onChange={this.handleUdfIdChange}
styles={{
root: {
width: "40%",
marginTop: "10px",
},
fieldGroup: {
backgroundColor: "var(--colorNeutralBackground1)",
border: "1px solid var(--colorNeutralStroke1)",
},
field: {
color: "var(--colorNeutralForeground2)",
},
subComponentStyles: {
label: {
root: {
color: "var(--colorNeutralForeground1)",
},
},
},
}}
/>{" "}
</FluentProvider>
<Label className="trigger-field">User Defined Function Body</Label>
<EditorReact
language={"javascript"}
content={udfBody}
isReadOnly={false}
ariaLabel={"User defined function body"}
onContentChanged={this.handleUdfBodyChange}
/>
</div>
);
}
}