Discard and save buttons working

This commit is contained in:
Laurent Nguyen 2024-03-27 08:47:12 +01:00
parent 821904d3b3
commit 79db486a5d

View File

@ -1,10 +1,12 @@
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ItemDefinition, PartitionKey, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { FluentProvider } from "@fluentui/react-components"; import { FluentProvider } from "@fluentui/react-components";
import Split from "@uiw/react-split"; import Split from "@uiw/react-split";
import { KeyCodes, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants"; import { KeyCodes, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { createDocument } from "Common/dataAccess/createDocument";
import { queryDocuments } from "Common/dataAccess/queryDocuments"; import { queryDocuments } from "Common/dataAccess/queryDocuments";
import { readDocument } from "Common/dataAccess/readDocument"; import { readDocument } from "Common/dataAccess/readDocument";
import { updateDocument } from "Common/dataAccess/updateDocument";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import { useDialog } from "Explorer/Controls/Dialog"; import { useDialog } from "Explorer/Controls/Dialog";
@ -32,6 +34,7 @@ import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as QueryUtils from "../../../Utils/QueryUtils"; import * as QueryUtils from "../../../Utils/QueryUtils";
import { extractPartitionKeyValues } from "../../../Utils/QueryUtils";
import DocumentId from "../../Tree/DocumentId"; import DocumentId from "../../Tree/DocumentId";
import TabsBase from "../TabsBase"; import TabsBase from "../TabsBase";
import { DocumentsTableComponent, DocumentsTableComponentItem } from "./DocumentsTableComponent"; import { DocumentsTableComponent, DocumentsTableComponentItem } from "./DocumentsTableComponent";
@ -74,8 +77,11 @@ export class DocumentsTabV2 extends TabsBase {
} }
// From TabsBase.renderObjectForEditor() // From TabsBase.renderObjectForEditor()
const renderObjectForEditor = (value: unknown, replacer: unknown, space: string | number): string => const renderObjectForEditor = (
JSON.stringify(value, replacer, space); value: unknown,
replacer: (this: any, key: string, value: any) => any,
space: string | number,
): string => JSON.stringify(value, replacer, space);
const DocumentsTabComponent: React.FunctionComponent<{ const DocumentsTabComponent: React.FunctionComponent<{
isPreferredApiMongoDB: boolean; isPreferredApiMongoDB: boolean;
@ -113,17 +119,17 @@ const DocumentsTabComponent: React.FunctionComponent<{
const [isExecutionError, setIsExecutionError] = useState<boolean>(false); const [isExecutionError, setIsExecutionError] = useState<boolean>(false);
const [onLoadStartKey, setOnLoadStartKey] = useState<number>(props.onLoadStartKey); const [onLoadStartKey, setOnLoadStartKey] = useState<number>(props.onLoadStartKey);
const [initialDocumentContent, setInitialDocumentContent] = useState<string>(undefined);
const [selectedDocumentContent, setSelectedDocumentContent] = useState<string>(undefined); const [selectedDocumentContent, setSelectedDocumentContent] = useState<string>(undefined);
const [selectedDocumentContentBaseline, setSelectedDocumentContentBaseline] = useState<unknown>(undefined); const [selectedDocumentContentBaseline, setSelectedDocumentContentBaseline] = useState<string>(undefined);
const [selectedDocumentId, setSelectedDocumentId] = useState<DocumentId>(undefined);
// Command buttons // Command buttons
const [editorState, setEditorState] = useState<ViewModels.DocumentExplorerState>( const [editorState, setEditorState] = useState<ViewModels.DocumentExplorerState>(
ViewModels.DocumentExplorerState.noDocumentSelected, ViewModels.DocumentExplorerState.noDocumentSelected,
); );
// Editor
const [initialDocumentContent, setInitialDocumentContent] = useState<string>(undefined);
const getNewDocumentButtonState = () => ({ const getNewDocumentButtonState = () => ({
enabled: (() => { enabled: (() => {
switch (editorState) { switch (editorState) {
@ -313,7 +319,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
}, []); }, []);
// If editor state changes, update the nav // If editor state changes, update the nav
useEffect(() => updateNavbarWithTabsButtons(), [editorState]); useEffect(() => updateNavbarWithTabsButtons(), [editorState, selectedDocumentContent]);
useEffect(() => { useEffect(() => {
if (documentsIterator) { if (documentsIterator) {
@ -343,11 +349,137 @@ const DocumentsTabComponent: React.FunctionComponent<{
}, [editorState /* TODO isEditorDirty depends on more than just editorState */]); }, [editorState /* TODO isEditorDirty depends on more than just editorState */]);
const initializeNewDocument = (): void => { const initializeNewDocument = (): void => {
this.selectedDocumentId(null); // this.selectedDocumentId(null);
const defaultDocument: string = renderObjectForEditor({ id: "replace_with_new_document_id" }, null, 4); const defaultDocument: string = renderObjectForEditor({ id: "replace_with_new_document_id" }, null, 4);
this.initialDocumentContent(defaultDocument); setInitialDocumentContent(defaultDocument);
this.selectedDocumentContent.setBaseline(defaultDocument); setSelectedDocumentContent(defaultDocument);
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid); setSelectedDocumentContentBaseline(defaultDocument);
setEditorState(ViewModels.DocumentExplorerState.newDocumentValid);
};
const onSaveNewDocumentClick = (): Promise<any> => {
setIsExecutionError(false);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: props.tabTitle,
});
const sanitizedContent = selectedDocumentContent.replace("\n", "");
const document = JSON.parse(sanitizedContent);
setIsExecuting(true);
return createDocument(props.collection, document)
.then(
(savedDocument: any) => {
const value: string = renderObjectForEditor(savedDocument || {}, null, 4);
setSelectedDocumentContentBaseline(value);
setInitialDocumentContent(value);
const partitionKeyValueArray: PartitionKey[] = extractPartitionKeyValues(
savedDocument,
partitionKey as PartitionKeyDefinition,
);
const id = new DocumentId(this, savedDocument, partitionKeyValueArray);
const ids = documentIds;
ids.push(id);
// this.selectedDocumentId(id);
setDocumentIds(ids);
setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
TelemetryProcessor.traceSuccess(
Action.CreateDocument,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: props.tabTitle,
},
startKey,
);
},
(error) => {
setIsExecutionError(true);
const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Create document failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.CreateDocument,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: props.tabTitle,
error: errorMessage,
errorStack: getErrorStack(error),
},
startKey,
);
},
)
.finally(() => setIsExecuting(false));
};
const onRevertNewDocumentClick = (): void => {
setInitialDocumentContent("");
setSelectedDocumentContent("");
setEditorState(ViewModels.DocumentExplorerState.noDocumentSelected);
};
const onSaveExistingDocumentClick = (): Promise<void> => {
// const selectedDocumentId = this.selectedDocumentId();
const documentContent = JSON.parse(selectedDocumentContent);
const partitionKeyValueArray: PartitionKey[] = extractPartitionKeyValues(
documentContent,
partitionKey as PartitionKeyDefinition,
);
selectedDocumentId.partitionKeyValue = partitionKeyValueArray;
setIsExecutionError(false);
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateDocument, {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: props.tabTitle,
});
setIsExecuting(true);
return updateDocument(props.collection, selectedDocumentId, documentContent)
.then(
(updatedDocument: any) => {
const value: string = renderObjectForEditor(updatedDocument || {}, null, 4);
setSelectedDocumentContentBaseline(value);
setInitialDocumentContent(value);
setSelectedDocumentContent(value);
documentIds.forEach((documentId: DocumentId) => {
if (documentId.rid === updatedDocument._rid) {
documentId.id(updatedDocument.id);
}
});
setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
TelemetryProcessor.traceSuccess(
Action.UpdateDocument,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: props.tabTitle,
},
startKey,
);
},
(error) => {
setIsExecutionError(true);
const errorMessage = getErrorMessage(error);
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.UpdateDocument,
{
dataExplorerArea: Constants.Areas.Tab,
tabTitle: props.tabTitle,
error: errorMessage,
errorStack: getErrorStack(error),
},
startKey,
);
},
)
.finally(() => setIsExecuting(false));
};
const onRevertExisitingDocumentClick = (): void => {
setSelectedDocumentContentBaseline(initialDocumentContent);
// this.initialDocumentContent.valueHasMutated();
setSelectedDocumentContent(selectedDocumentContentBaseline);
// setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
}; };
const onShowFilterClick = () => { const onShowFilterClick = () => {
@ -613,7 +745,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
return true; return true;
})(); })();
const getTabsButtons = useCallback((): CommandButtonComponentProps[] => { const getTabsButtons = (): CommandButtonComponentProps[] => {
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) { if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
// All the following buttons require write access // All the following buttons require write access
return []; return [];
@ -639,12 +771,12 @@ const DocumentsTabComponent: React.FunctionComponent<{
buttons.push({ buttons.push({
iconSrc: SaveIcon, iconSrc: SaveIcon,
iconAlt: label, iconAlt: label,
onCommandClick: undefined, // TODO implement: onSaveNewDocumentClick, onCommandClick: onSaveNewDocumentClick,
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
disabled: disabled:
getSaveNewDocumentButtonState().enabled || useSelectedNode.getState().isQueryCopilotCollectionSelected(), !getSaveNewDocumentButtonState().enabled || useSelectedNode.getState().isQueryCopilotCollectionSelected(),
}); });
} }
@ -653,7 +785,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
buttons.push({ buttons.push({
iconSrc: DiscardIcon, iconSrc: DiscardIcon,
iconAlt: label, iconAlt: label,
onCommandClick: undefined, // TODO implement: onRevertNewDocumentClick, onCommandClick: onRevertNewDocumentClick,
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
@ -668,7 +800,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
buttons.push({ buttons.push({
iconSrc: SaveIcon, iconSrc: SaveIcon,
iconAlt: label, iconAlt: label,
onCommandClick: undefined, // TODO implement: onSaveExistingDocumentClick, onCommandClick: onSaveExistingDocumentClick,
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
@ -683,7 +815,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
buttons.push({ buttons.push({
iconSrc: DiscardIcon, iconSrc: DiscardIcon,
iconAlt: label, iconAlt: label,
onCommandClick: undefined, // TODO implement: onRevertExisitingDocumentClick, onCommandClick: onRevertExisitingDocumentClick,
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: false, hasPopup: false,
@ -713,7 +845,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
} }
return buttons; return buttons;
}, [editorState]); };
const _isQueryCopilotSampleContainer = const _isQueryCopilotSampleContainer =
props.collection?.isSampleCollection && props.collection?.isSampleCollection &&
@ -763,6 +895,8 @@ const DocumentsTabComponent: React.FunctionComponent<{
* @param index * @param index
*/ */
const onDocumentClicked = (index: number) => { const onDocumentClicked = (index: number) => {
setSelectedDocumentId(documentIds[index]);
if (isEditorDirty()) { if (isEditorDirty()) {
useDialog useDialog
.getState() .getState()
@ -794,8 +928,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
if (documentId) { if (documentId) {
const content: string = renderObjectForEditor(documentContent, null, 4); const content: string = renderObjectForEditor(documentContent, null, 4);
setSelectedDocumentContentBaseline(content); setSelectedDocumentContentBaseline(content);
setInitialDocumentContent(content); // TODO Remove this?
setSelectedDocumentContent(content); setSelectedDocumentContent(content);
setInitialDocumentContent(content);
const newState = documentId const newState = documentId
? ViewModels.DocumentExplorerState.exisitingDocumentNoEdits ? ViewModels.DocumentExplorerState.exisitingDocumentNoEdits
: ViewModels.DocumentExplorerState.newDocumentValid; : ViewModels.DocumentExplorerState.newDocumentValid;
@ -806,6 +941,14 @@ const DocumentsTabComponent: React.FunctionComponent<{
const _onEditorContentChange = (newContent: string): void => { const _onEditorContentChange = (newContent: string): void => {
setSelectedDocumentContent(newContent); setSelectedDocumentContent(newContent);
if (
selectedDocumentContentBaseline === initialDocumentContent &&
selectedDocumentContent === initialDocumentContent
) {
setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
return;
}
try { try {
JSON.parse(newContent); JSON.parse(newContent);
onValidDocumentEdit(); onValidDocumentEdit();
@ -824,7 +967,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
} }
setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid); setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyValid);
} };
const onInvalidDocumentEdit = (): void => { const onInvalidDocumentEdit = (): void => {
if ( if (
@ -842,8 +985,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid); setEditorState(ViewModels.DocumentExplorerState.exisitingDocumentDirtyInvalid);
return; return;
} }
} };
const tableContainerRef = useRef(null); const tableContainerRef = useRef(null);
const [tableContainerSizePx, setTableContainerSizePx] = useState<{ height: number; width: number }>(undefined); const [tableContainerSizePx, setTableContainerSizePx] = useState<{ height: number; width: number }>(undefined);
@ -893,7 +1035,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
<button <button
className="filterbtnstyle queryButton" className="filterbtnstyle queryButton"
onClick={onShowFilterClick} onClick={onShowFilterClick}
/*data-bind="click: onShowFilterClick"*/ /*data-bind="click: onShowFilterClick"*/
> >
Edit Filter Edit Filter
</button> </button>
@ -943,7 +1085,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
} }
value={filterContent} value={filterContent}
onChange={(e) => setFilterContent(e.target.value)} onChange={(e) => setFilterContent(e.target.value)}
/* /*
data-bind=" data-bind="
W attr:{ W attr:{
placeholder:isPreferredApiMongoDB?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.' placeholder:isPreferredApiMongoDB?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.'
@ -996,7 +1138,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
tabIndex={0} tabIndex={0}
onClick={() => onHideFilterClick()} onClick={() => onHideFilterClick()}
onKeyDown={onCloseButtonKeyDown} onKeyDown={onCloseButtonKeyDown}
/*data-bind="click: onHideFilterClick, event: { keydown: onCloseButtonKeyDown }"*/ /*data-bind="click: onHideFilterClick, event: { keydown: onCloseButtonKeyDown }"*/
> >
<img src={CloseIcon} style={{ height: 14, width: 14 }} alt="Hide filter" /> <img src={CloseIcon} style={{ height: 14, width: 14 }} alt="Hide filter" />
</span> </span>
@ -1029,7 +1171,7 @@ const DocumentsTabComponent: React.FunctionComponent<{
{shouldShowEditor && ( {shouldShowEditor && (
<EditorReact <EditorReact
language={"json"} language={"json"}
content={initialDocumentContent} content={selectedDocumentContent}
isReadOnly={false} isReadOnly={false}
ariaLabel={"Document editor"} ariaLabel={"Document editor"}
lineNumbers={"on"} lineNumbers={"on"}