From 2f9436f7798bdae62dd86df5107d8720276f100d Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 14 Nov 2025 14:18:14 -0800 Subject: [PATCH 1/2] feat(crud): add tooltip for N/A count --- .../src/components/crud-toolbar.tsx | 31 ++++++++++++++----- .../compass-crud/src/stores/crud-store.ts | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/compass-crud/src/components/crud-toolbar.tsx b/packages/compass-crud/src/components/crud-toolbar.tsx index 3481581b3b7..8cf56e0f3ba 100644 --- a/packages/compass-crud/src/components/crud-toolbar.tsx +++ b/packages/compass-crud/src/components/crud-toolbar.tsx @@ -21,10 +21,11 @@ import { useContextMenuGroups, usePersistedState, AtlasSkillsBanner, + Tooltip, } 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 { COUNT_MAX_TIME_MS_CAP, type DocumentView } from '../stores/crud-store'; import { AddDataMenu } from './add-data-menu'; import { usePreference } from 'compass-preferences-model/provider'; import UpdateMenu from './update-data-menu'; @@ -87,6 +88,12 @@ const loaderContainerStyles = css({ paddingRight: spacing[200], }); +const countUnavailableTextStyles = css({ + textDecoration: 'underline', + textDecorationStyle: 'dotted', + textUnderlineOffset: '3px', +}); + type ExportDataOption = 'export-query' | 'export-full-collection'; const exportDataActions: MenuAction[] = [ { action: 'export-query', label: 'Export query results' }, @@ -109,6 +116,8 @@ 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.'; +const COUNT_UNAVAILABLE_TOOLTIP = `The count is not available for this query. This can happen when the count operation fails or exceeds the maxTimeMS of ${COUNT_MAX_TIME_MS_CAP}.`; + type ErrorWithPossibleCode = Error & { code?: { value: number; @@ -198,11 +207,6 @@ const CrudToolbar: React.FunctionComponent = ({ SkillsBannerContextEnum.Documents ); - const displayedDocumentCount = useMemo( - () => (loadingCount ? '' : `${count ?? 'N/A'}`), - [loadingCount, count] - ); - const onClickRefreshDocuments = useCallback(() => { track('Query Results Refreshed', {}, connectionInfoRef.current); refreshDocuments(); @@ -416,7 +420,20 @@ const CrudToolbar: React.FunctionComponent = ({ {start} – {end}{' '} - {displayedDocumentCount && `of ${displayedDocumentCount}`} + {!loadingCount && ( + + {'of '} + {count ?? ( + N/A + } + > + {COUNT_UNAVAILABLE_TOOLTIP} + + )} + + )} {loadingCount && (
diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 02ab3ee5e56..176f8f701cb 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -253,7 +253,7 @@ const DEFAULT_INITIAL_MAX_TIME_MS = 60000; * We want to make sure `count` does not hold back the query results for too * long after docs are returned. */ -const COUNT_MAX_TIME_MS_CAP = 5000; +export const COUNT_MAX_TIME_MS_CAP = 5000; /** * The key we use to persist the user selected maximum documents per page for From b85e684be001c808f6598301c26653a2d634b142 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Mon, 17 Nov 2025 09:16:09 -0800 Subject: [PATCH 2/2] fixup: show maxTimeMS that was used for count, not the default --- .../src/components/crud-toolbar.spec.tsx | 28 +++++++++++++++++++ .../src/components/crud-toolbar.tsx | 18 +++++++++--- .../src/components/document-list.tsx | 3 ++ .../src/stores/crud-store.spec.ts | 1 + .../compass-crud/src/stores/crud-store.ts | 3 ++ 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/compass-crud/src/components/crud-toolbar.spec.tsx b/packages/compass-crud/src/components/crud-toolbar.spec.tsx index 3e490889ea2..8b013b56523 100644 --- a/packages/compass-crud/src/components/crud-toolbar.spec.tsx +++ b/packages/compass-crud/src/components/crud-toolbar.spec.tsx @@ -6,6 +6,7 @@ import { within, userEvent, renderWithConnections, + waitFor, } from '@mongodb-js/testing-library-compass'; import type { PreferencesAccess } from 'compass-preferences-model'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; @@ -37,6 +38,7 @@ describe('CrudToolbar Component', function () { count={55} end={20} getPage={noop} + lastCountRunMaxTimeMS={12345} insertDataHandler={noop} loadingCount={false} isFetching={false} @@ -499,6 +501,31 @@ describe('CrudToolbar Component', function () { }); }); + describe('when count the count is unavailable', function () { + it('shows N/A with the count maxTimeMS', async function () { + renderCrudToolbar({ + count: undefined, + }); + expect(screen.getByText('N/A')).to.be.visible; + + const naText = screen.getByTestId('crud-document-count-unavailable'); + expect(naText).to.be.visible; + userEvent.hover(naText); + + await waitFor( + function () { + expect(screen.getByRole('tooltip')).to.exist; + }, + { + timeout: 5000, + } + ); + + const tooltipText = screen.getByRole('tooltip').textContent; + expect(tooltipText).to.include('maxTimeMS of 12345.'); + }); + }); + describe('context menu', function () { beforeEach(async function () { await preferences.savePreferences({ enableImportExport: true }); @@ -897,6 +924,7 @@ describe('CrudToolbar Component', function () { isFetching={false} docsPerPage={25} isWritable + lastCountRunMaxTimeMS={1234} instanceDescription="" onApplyClicked={noop} onResetClicked={noop} diff --git a/packages/compass-crud/src/components/crud-toolbar.tsx b/packages/compass-crud/src/components/crud-toolbar.tsx index 8cf56e0f3ba..d9af12a476e 100644 --- a/packages/compass-crud/src/components/crud-toolbar.tsx +++ b/packages/compass-crud/src/components/crud-toolbar.tsx @@ -25,7 +25,7 @@ import { } from '@mongodb-js/compass-components'; import type { MenuAction, Signal } from '@mongodb-js/compass-components'; import { ViewSwitcher } from './view-switcher'; -import { COUNT_MAX_TIME_MS_CAP, type DocumentView } from '../stores/crud-store'; +import { type DocumentView } from '../stores/crud-store'; import { AddDataMenu } from './add-data-menu'; import { usePreference } from 'compass-preferences-model/provider'; import UpdateMenu from './update-data-menu'; @@ -116,7 +116,8 @@ 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.'; -const COUNT_UNAVAILABLE_TOOLTIP = `The count is not available for this query. This can happen when the count operation fails or exceeds the maxTimeMS of ${COUNT_MAX_TIME_MS_CAP}.`; +const countUnavailableTooltipText = (maxTimeMS: number) => + `The count is not available for this query. This can happen when the count operation fails or exceeds the maxTimeMS of ${maxTimeMS}.`; type ErrorWithPossibleCode = Error & { code?: { @@ -141,6 +142,7 @@ export type CrudToolbarProps = { instanceDescription: string; isWritable: boolean; isFetching: boolean; + lastCountRunMaxTimeMS: number; loadingCount: boolean; onApplyClicked: () => void; onResetClicked: () => void; @@ -173,6 +175,7 @@ const CrudToolbar: React.FunctionComponent = ({ instanceDescription, isWritable, isFetching, + lastCountRunMaxTimeMS, loadingCount, onApplyClicked, onResetClicked, @@ -426,10 +429,17 @@ const CrudToolbar: React.FunctionComponent = ({ {count ?? ( N/A + + N/A + } > - {COUNT_UNAVAILABLE_TOOLTIP} + + {countUnavailableTooltipText(lastCountRunMaxTimeMS)} + )} diff --git a/packages/compass-crud/src/components/document-list.tsx b/packages/compass-crud/src/components/document-list.tsx index e034a0e3f27..26e4851009e 100644 --- a/packages/compass-crud/src/components/document-list.tsx +++ b/packages/compass-crud/src/components/document-list.tsx @@ -121,6 +121,7 @@ export type DocumentListProps = { CrudToolbarProps, | 'error' | 'count' + | 'lastCountRunMaxTimeMS' | 'loadingCount' | 'start' | 'end' @@ -278,6 +279,7 @@ const DocumentList: React.FunctionComponent = (props) => { view, error, count, + lastCountRunMaxTimeMS, loadingCount, start, end, @@ -534,6 +536,7 @@ const DocumentList: React.FunctionComponent = (props) => { error={error} count={count} isFetching={isFetching} + lastCountRunMaxTimeMS={lastCountRunMaxTimeMS} loadingCount={loadingCount} start={start} end={end} diff --git a/packages/compass-crud/src/stores/crud-store.spec.ts b/packages/compass-crud/src/stores/crud-store.spec.ts index d2766b88fa0..529a69abb60 100644 --- a/packages/compass-crud/src/stores/crud-store.spec.ts +++ b/packages/compass-crud/src/stores/crud-store.spec.ts @@ -316,6 +316,7 @@ describe('store', function () { status: 'closed', }, debouncingLoad: false, + lastCountRunMaxTimeMS: 5000, loadingCount: false, collection: 'test', count: null, diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 176f8f701cb..73e748fafda 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -345,6 +345,7 @@ type CrudState = { isReadonly: boolean; isTimeSeries: boolean; status: DOCUMENTS_STATUSES; + lastCountRunMaxTimeMS: number; debouncingLoad: boolean; loadingCount: boolean; shardKeys: null | BSONObject; @@ -453,6 +454,7 @@ class CrudStoreImpl status: DOCUMENTS_STATUS_INITIAL, debouncingLoad: false, loadingCount: false, + lastCountRunMaxTimeMS: COUNT_MAX_TIME_MS_CAP, shardKeys: null, resultId: resultId(), isWritable: this.instance.isWritable, @@ -1779,6 +1781,7 @@ class CrudStoreImpl // This is so that the UI can update to show that we're fetching this.setState({ + lastCountRunMaxTimeMS: countOptions.maxTimeMS, status: DOCUMENTS_STATUS_FETCHING, abortController, error: null,