import type AppRegistry from 'hadron-app-registry'; import React, { useCallback, useMemo, useRef } from 'react'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; import { Body, DropdownMenuButton, Icon, IconButton, SpinLoader, css, spacing, WarningSummary, ErrorSummary, } from '@mongodb-js/compass-components'; import type { MenuAction, Signal } from '@mongodb-js/compass-components'; import { ViewSwitcher } from './view-switcher'; import type { DocumentView } from '../stores/crud-store'; import { AddDataMenu } from './add-data-menu'; import { usePreference } from 'compass-preferences-model'; import UpdateMenu from './update-data-menu'; import DeleteMenu from './delete-data-menu'; const { track } = createLoggerAndTelemetry('COMPASS-CRUD-UI'); const crudQueryBarStyles = css({ width: '100%', position: 'relative', }); const crudToolbarStyles = css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: spacing[3], padding: spacing[3], }); const crudBarStyles = css({ width: '100%', display: 'flex', gap: spacing[2], justifyContent: 'space-between', }); const toolbarLeftActionStyles = css({ display: 'flex', alignItems: 'center', gap: spacing[2], }); const toolbarRightActionStyles = css({ display: 'flex', alignItems: 'center', gap: spacing[2], }); const exportCollectionButtonStyles = css({ whiteSpace: 'nowrap', }); type ExportDataOption = 'export-query' | 'export-full-collection'; const exportDataActions: MenuAction[] = [ { action: 'export-query', label: 'Export query results' }, { action: 'export-full-collection', label: 'Export the full collection' }, ]; const OUTDATED_WARNING = `The content is outdated and no longer in sync with the current query. Press "Find" again to see the results for the current query.`; // From https://github.com/mongodb/mongo/blob/master/src/mongo/base/error_codes.yml#L86 const ERROR_CODE_OPERATION_TIMED_OUT = 50; const INCREASE_MAX_TIME_MS_HINT = 'Operation exceeded time limit. Please try increasing the maxTimeMS for the query in the expanded filter options.'; type ErrorWithPossibleCode = Error & { code?: { value: number; }; }; function isOperationTimedOutError(err: ErrorWithPossibleCode) { return ( err.name === 'MongoServerError' && err.code?.value === ERROR_CODE_OPERATION_TIMED_OUT ); } export type CrudToolbarProps = { activeDocumentView: DocumentView; count?: number; end: number; error?: ErrorWithPossibleCode | null; getPage: (page: number) => void; insertDataHandler: (openInsertKey: 'insert-document' | 'import-file') => void; instanceDescription: string; isExportable: boolean; isWritable: boolean; loadingCount: boolean; localAppRegistry: Pick; onApplyClicked: () => void; onResetClicked: () => void; onUpdateButtonClicked: () => void; onDeleteButtonClicked: () => void; openExportFileDialog: (exportFullCollection?: boolean) => void; outdated: boolean; page: number; readonly: boolean; refreshDocuments: () => void; resultId: string; start: number; viewSwitchHandler: (view: DocumentView) => void; insights?: Signal; queryLimit?: number; querySkip?: number; }; const CrudToolbar: React.FunctionComponent = ({ activeDocumentView, count, end, error, getPage, insertDataHandler, instanceDescription, isExportable, isWritable, loadingCount, localAppRegistry, onApplyClicked, onResetClicked, onUpdateButtonClicked, onDeleteButtonClicked, openExportFileDialog, outdated, page, readonly, refreshDocuments, resultId, start, viewSwitchHandler, insights, queryLimit, querySkip, }) => { const queryBarRole = localAppRegistry.getRole('Query.QueryBar')![0]; const queryBarRef = useRef( isExportable ? { component: queryBarRole.component, store: localAppRegistry.getStore(queryBarRole.storeName!), } : null ); const displayedDocumentCount = useMemo( () => (loadingCount ? '' : `${count ?? 'N/A'}`), [loadingCount, count] ); const onClickRefreshDocuments = useCallback(() => { track('Query Results Refreshed'); refreshDocuments(); }, [refreshDocuments]); const QueryBarComponent = isExportable ? queryBarRef.current!.component : null; const prevButtonDisabled = useMemo(() => page === 0, [page]); const nextButtonDisabled = useMemo( // If we don't know the count, we can't know if there are more pages. () => count === undefined || count === null ? false : 20 * (page + 1) >= count, [count, page] ); const enableExplainPlan = usePreference('enableExplainPlan', React); const shouldDisableBulkOp = useMemo( () => querySkip || queryLimit, [querySkip, queryLimit] ); return (
{isExportable && QueryBarComponent && ( )}
{!readonly && ( )} data-testid="crud-export-collection" actions={exportDataActions} onAction={(action: ExportDataOption) => openExportFileDialog(action === 'export-full-collection') } buttonText="Export Data" buttonProps={{ className: exportCollectionButtonStyles, size: 'xsmall', leftGlyph: , }} /> {!readonly && ( )} {!readonly && ( )}
{start} – {end}{' '} {displayedDocumentCount && `of ${displayedDocumentCount}`} {loadingCount && ( )} {!loadingCount && ( )}
getPage(page - 1)} disabled={prevButtonDisabled} > getPage(page + 1)} disabled={nextButtonDisabled} >
{error && ( )} {outdated && !error && ( )}
); }; export { CrudToolbar };