diff --git a/packages/compass-components/src/components/document-list/document-edit-actions-footer.tsx b/packages/compass-components/src/components/document-list/document-edit-actions-footer.tsx index 6468e605f5b..4dffbae7253 100644 --- a/packages/compass-components/src/components/document-list/document-edit-actions-footer.tsx +++ b/packages/compass-components/src/components/document-list/document-edit-actions-footer.tsx @@ -6,6 +6,7 @@ import { css } from '@leafygreen-ui/emotion'; import { palette } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; import { useDarkMode } from '../../hooks/use-theme'; +import { showErrorDetails } from '../../hooks/use-error-details'; type Status = | 'Initial' @@ -81,13 +82,30 @@ function useHadronDocumentStatus( ? 'Deleting' : 'Initial'; }); - const [errorMessage, setErrorMessage] = useState(null); + const [error, setError] = useState<{ + message: string; + details?: Record; + } | null>(null); const invalidElementsRef = useRef(new Set()); - const updateStatus = useCallback((newStatus: Status, errorMessage = null) => { - setStatus(newStatus); - setErrorMessage(errorMessage); - }, []); + const updateStatus = useCallback( + ( + newStatus: Status, + error: Error | null = null, + errorDetails?: Record + ) => { + setStatus(newStatus); + setError( + error + ? { + message: error?.message, + details: errorDetails, + } + : null + ); + }, + [] + ); useEffect(() => { if (status !== 'Initial') { @@ -128,8 +146,11 @@ function useHadronDocumentStatus( const onUpdateSuccess = () => { updateStatus('UpdateSuccess'); }; - const onUpdateError = (err: string) => { - updateStatus('UpdateError', err); + const onUpdateError = ( + err: Error, + errorDetails?: Record + ) => { + updateStatus('UpdateError', err, errorDetails); }; const onRemoveStart = () => { updateStatus('DeleteStart'); @@ -137,8 +158,11 @@ function useHadronDocumentStatus( const onRemoveSuccess = () => { updateStatus('DeleteSuccess'); }; - const onRemoveError = (err: string) => { - updateStatus('DeleteError', err); + const onRemoveError = ( + err: Error, + errorDetails?: Record + ) => { + updateStatus('DeleteError', err, errorDetails); }; doc.on(Element.Events.Added, onUpdate); @@ -183,30 +207,30 @@ function useHadronDocumentStatus( } }, [status, updateStatus]); - return { status, updateStatus, errorMessage }; + return { status, updateStatus, error }; } const container = css({ display: 'flex', - paddingTop: spacing[2], - paddingRight: spacing[2], - paddingBottom: spacing[2], - paddingLeft: spacing[3], + paddingTop: spacing[200], + paddingRight: spacing[200], + paddingBottom: spacing[200], + paddingLeft: spacing[400], alignItems: 'center', - gap: spacing[2], + gap: spacing[200], borderBottomLeftRadius: 'inherit', borderBottomRightRadius: 'inherit', }); const message = css({ - overflow: 'hidden', - textOverflow: 'ellipsis', + overflow: 'scroll', }); const buttonGroup = css({ display: 'flex', marginLeft: 'auto', - gap: spacing[2], + gap: spacing[200], + flexShrink: 0, }); const button = css({ @@ -275,7 +299,7 @@ const EditActionsFooter: React.FunctionComponent<{ const { status: _status, updateStatus, - errorMessage, + error, } = useHadronDocumentStatus(doc, editing, deleting); const darkMode = useDarkMode(); @@ -303,10 +327,25 @@ const EditActionsFooter: React.FunctionComponent<{ data-status={status} >
- {errorMessage ?? statusMessage} + {error?.message ?? statusMessage}
{!isSuccess(status) && (
+ {error?.details && ( + + )} - - - ); -} - -export { ErrorDetailsModal }; diff --git a/packages/compass-components/src/hooks/use-confirmation.tsx b/packages/compass-components/src/hooks/use-confirmation.tsx index cc94cff7027..1fd30fd1bfa 100644 --- a/packages/compass-components/src/hooks/use-confirmation.tsx +++ b/packages/compass-components/src/hooks/use-confirmation.tsx @@ -2,6 +2,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { Variant as ConfirmationModalVariant } from '@leafygreen-ui/confirmation-modal'; import ConfirmationModal from '../components/modals/confirmation-modal'; import { css } from '@leafygreen-ui/emotion'; +import type { ButtonProps } from '@leafygreen-ui/button'; export { ConfirmationModalVariant }; @@ -11,6 +12,7 @@ type ConfirmationProperties = Partial< Pick > & { buttonText?: React.ReactNode; + confirmButtonProps?: Omit; hideConfirmButton?: boolean; hideCancelButton?: boolean; description?: React.ReactNode; @@ -100,8 +102,28 @@ export const ConfirmationModalArea: React.FC = ({ children }) => { }); const callbackRef = useRef(); + const listenerRef = + useRef<(event: CustomEvent) => void>(); + const contextValue = React.useMemo( - () => ({ showConfirmation, isMounted: true }), + () => ({ + showConfirmation: (props: ConfirmationProperties) => { + return new Promise((resolve, reject) => { + const event = new CustomEvent( + 'show-confirmation', + { + detail: { + props: { ...props, confirmationId: ++confirmationId }, + resolve, + reject, + }, + } + ); + listenerRef.current?.(event); + }); + }, + isMounted: true, + }), [] ); @@ -127,6 +149,7 @@ export const ConfirmationModalArea: React.FC = ({ children }) => { }; props.signal?.addEventListener('abort', onAbort); }; + listenerRef.current = listener; globalConfirmation.addEventListener('show-confirmation', listener); return () => { globalConfirmation.removeEventListener('show-confirmation', listener); @@ -170,6 +193,7 @@ export const ConfirmationModalArea: React.FC = ({ children }) => { : undefined, children: confirmationProps.buttonText ?? 'Confirm', onClick: handleConfirm, + ...confirmationProps.confirmButtonProps, }} cancelButtonProps={{ className: confirmationProps.hideCancelButton diff --git a/packages/compass-components/src/hooks/use-error-details.tsx b/packages/compass-components/src/hooks/use-error-details.tsx new file mode 100644 index 00000000000..99302afa1c2 --- /dev/null +++ b/packages/compass-components/src/hooks/use-error-details.tsx @@ -0,0 +1,38 @@ +import { + type showConfirmation as originalShowConfirmation, + showConfirmation, +} from './use-confirmation'; +import { Code } from '../components/leafygreen'; +import React from 'react'; +import { ButtonVariant } from '..'; + +const getShowErrorDetails = ( + showConfirmation: typeof originalShowConfirmation +) => { + return ({ + details, + closeAction, + }: { + details: Record; + closeAction: 'back' | 'close'; + }) => + void showConfirmation({ + title: 'Error details', + description: ( + + {JSON.stringify(details, undefined, 2)} + + ), + hideCancelButton: true, + buttonText: closeAction.replace(/\b\w/g, (c) => c.toUpperCase()), + confirmButtonProps: { + variant: ButtonVariant.Default, + }, + }); +}; + +export const showErrorDetails = getShowErrorDetails(showConfirmation); diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index d8c05eb97be..2b47da41542 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -92,7 +92,6 @@ export { ModalBody } from './components/modals/modal-body'; export { ModalHeader } from './components/modals/modal-header'; export { FormModal } from './components/modals/form-modal'; export { InfoModal } from './components/modals/info-modal'; -export { ErrorDetailsModal } from './components/modals/error-details-modal'; export type { FileInputBackend, @@ -183,6 +182,7 @@ export { ConfirmationModalArea, showConfirmation, } from './hooks/use-confirmation'; +export { showErrorDetails } from './hooks/use-error-details'; export { useHotkeys, formatHotkey, diff --git a/packages/compass-crud/src/actions/index.ts b/packages/compass-crud/src/actions/index.ts index 4eb718b3053..5600c4c103c 100644 --- a/packages/compass-crud/src/actions/index.ts +++ b/packages/compass-crud/src/actions/index.ts @@ -19,8 +19,6 @@ const configureActions = () => { 'toggleInsertDocumentView', 'toggleInsertDocument', 'openInsertDocumentDialog', - 'openErrorDetailsDialog', - 'closeErrorDetailsDialog', 'openBulkUpdateModal', 'updateBulkUpdatePreview', 'runBulkUpdate', diff --git a/packages/compass-crud/src/components/document-json-view.tsx b/packages/compass-crud/src/components/document-json-view.tsx index 0cabaaf236d..700857879de 100644 --- a/packages/compass-crud/src/components/document-json-view.tsx +++ b/packages/compass-crud/src/components/document-json-view.tsx @@ -33,7 +33,6 @@ export type DocumentJsonViewProps = { | 'removeDocument' | 'replaceDocument' | 'updateDocument' - | 'openErrorDetailsDialog' | 'openInsertDocumentDialog' >; diff --git a/packages/compass-crud/src/components/document-list-view.spec.tsx b/packages/compass-crud/src/components/document-list-view.spec.tsx index bfb28904fb0..536f48e66c2 100644 --- a/packages/compass-crud/src/components/document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/document-list-view.spec.tsx @@ -21,7 +21,6 @@ describe('', function () { replaceDocument={sinon.spy()} updateDocument={sinon.spy()} openInsertDocumentDialog={sinon.spy()} - openErrorDetailsDialog={sinon.spy()} /> ); diff --git a/packages/compass-crud/src/components/document-list-view.tsx b/packages/compass-crud/src/components/document-list-view.tsx index 825fe7538aa..938ecc6fd1b 100644 --- a/packages/compass-crud/src/components/document-list-view.tsx +++ b/packages/compass-crud/src/components/document-list-view.tsx @@ -97,7 +97,6 @@ class DocumentListView extends React.Component { replaceDocument: PropTypes.func, updateDocument: PropTypes.func, openInsertDocumentDialog: PropTypes.func, - openErrorDetailsDialog: PropTypes.func, copyToClipboard: PropTypes.func, className: PropTypes.string, }; diff --git a/packages/compass-crud/src/components/document-list.tsx b/packages/compass-crud/src/components/document-list.tsx index 6ab97bcb1d6..f9021c0cd1d 100644 --- a/packages/compass-crud/src/components/document-list.tsx +++ b/packages/compass-crud/src/components/document-list.tsx @@ -9,7 +9,6 @@ import { WorkspaceContainer, spacing, withDarkMode, - ErrorDetailsModal, } from '@mongodb-js/compass-components'; import type { InsertDocumentDialogProps } from './insert-document-dialog'; import InsertDocumentDialog from './insert-document-dialog'; @@ -30,13 +29,7 @@ import { DOCUMENTS_STATUS_FETCHING, DOCUMENTS_STATUS_FETCHED_INITIAL, } from '../constants/documents-statuses'; -import type { - CrudStore, - BSONObject, - DocumentView, - ErrorDetailsDialogOptions, - ErrorDetailsDialogState, -} from '../stores/crud-store'; +import type { CrudStore, BSONObject, DocumentView } from '../stores/crud-store'; import { getToolbarSignal } from '../utils/toolbar-signal'; import BulkDeleteModal from './bulk-delete-modal'; import { useTabState } from '@mongodb-js/compass-workspaces/provider'; @@ -73,8 +66,6 @@ const loaderContainerStyles = css({ export type DocumentListProps = { store: CrudStore; openInsertDocumentDialog?: (doc: BSONObject, cloned: boolean) => void; - openErrorDetailsDialog: (options: ErrorDetailsDialogOptions) => void; - closeErrorDetailsDialog: () => void; openBulkUpdateModal: () => void; updateBulkUpdatePreview: (updateText: string) => void; runBulkUpdate: () => void; @@ -82,7 +73,6 @@ export type DocumentListProps = { openImportFileDialog?: (origin: 'empty-state' | 'crud-toolbar') => void; docs: Document[]; view: DocumentView; - errorDetails: ErrorDetailsDialogState; insert: Partial & Required< Pick< @@ -301,10 +291,7 @@ const DocumentList: React.FunctionComponent = (props) => { resultId, isCollectionScan, isSearchIndexesSupported, - errorDetails, openInsertDocumentDialog, - openErrorDetailsDialog, - closeErrorDetailsDialog, openImportFileDialog, openBulkUpdateModal, docs, @@ -590,20 +577,8 @@ const DocumentList: React.FunctionComponent = (props) => { version={version} ns={ns} updateComment={updateComment} - showErrorDetails={() => - openErrorDetailsDialog({ - details: insert.error.info || {}, - closeAction: 'back', - }) - } {...insert} /> - void; logger?: Logger; track?: TrackFunction; - showErrorDetails: () => void; }; const DocumentOrJsonView: React.FC<{ @@ -135,7 +135,6 @@ const InsertDocumentDialog: React.FC = ({ updateJsonDoc, updateComment, closeInsertDocumentDialog, - showErrorDetails, }) => { const [invalidElements, setInvalidElements] = useState( [] @@ -346,7 +345,12 @@ const InsertDocumentDialog: React.FC = ({