diff --git a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx index d0a8a627128..617aa457c45 100644 --- a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx +++ b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx @@ -9,7 +9,6 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { IndexesToolbar } from './indexes-toolbar'; -import type { Document } from 'mongodb'; describe('IndexesToolbar Component', function () { const renderIndexesToolbar = ( @@ -27,7 +26,6 @@ describe('IndexesToolbar Component', function () { onRefreshIndexes={() => {}} isSearchIndexesSupported={false} isRefreshing={false} - collectionStats={{ index_count: 0, index_size: 0, pipeline: [] }} onIndexViewChanged={() => {}} onCreateRegularIndexClick={() => {}} onCreateSearchIndexClick={() => {}} @@ -144,21 +142,26 @@ describe('IndexesToolbar Component', function () { }); describe('and pipeline is not queryable', function () { - it('should disable the create search index button', function () { - const pipelineMock: Document[] = [ - { $project: { newField: 'testValue' } }, - ]; - const mockCollectionStats = { - index_count: 0, - index_size: 0, - pipeline: pipelineMock, - }; + it('should hide the toolbar when there are no search indexes', function () { + renderIndexesToolbar({ + isReadonlyView: true, + serverVersion: '8.1.0', + indexView: 'search-indexes', + isViewPipelineSearchQueryable: false, + hasSearchIndexes: false, + }); + + expect(screen.queryByTestId('indexes-toolbar')).to.not.exist; + expect(screen.queryByText('Create Search Index')).to.not.exist; + }); + it('should render the create search index button disabled when there are existing search indexes', function () { renderIndexesToolbar({ isReadonlyView: true, serverVersion: '8.1.0', indexView: 'search-indexes', - collectionStats: mockCollectionStats, + isViewPipelineSearchQueryable: false, + hasSearchIndexes: true, }); expect(screen.getByText('Create Search Index')).to.be.visible; diff --git a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx index 093975e9a1f..0c8faea6cec 100644 --- a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx +++ b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx @@ -25,9 +25,9 @@ import { createSearchIndexOpened } from '../../modules/search-indexes'; import { createIndexOpened } from '../../modules/create-index'; import type { IndexView } from '../../modules/index-view'; import { indexViewChanged } from '../../modules/index-view'; -import type { CollectionStats } from '../../modules/collection-stats'; -import type { Document } from 'mongodb'; import { VIEW_PIPELINE_UTILS } from '@mongodb-js/mongodb-constants'; +import { selectIsViewSearchCompatible } from '../../utils/is-view-search-compatible'; +import { shouldShowIndexesToolbarButtons } from '../../utils/should-show-indexes-toolbar-buttons'; import { useSearchActivationProgramP1, useTelemetry, @@ -44,6 +44,9 @@ const toolbarButtonsContainer = css({ const indexesToolbarContainerStyles = css({ padding: spacing[400], paddingBottom: 0, + display: 'flex', + flexDirection: 'column', + gap: spacing[200], }); const alignSelfEndStyles = css({ @@ -87,8 +90,8 @@ type IndexesToolbarProps = { onCreateSearchIndexClick: () => void; writeStateDescription?: string; isSearchIndexesSupported: boolean; - // via withPreferences: - collectionStats: CollectionStats; + hasSearchIndexes?: boolean; + isViewPipelineSearchQueryable?: boolean; }; export const IndexesToolbar: React.FunctionComponent = ({ @@ -107,7 +110,8 @@ export const IndexesToolbar: React.FunctionComponent = ({ onRefreshIndexes, onIndexViewChanged, serverVersion, - collectionStats, + hasSearchIndexes = false, + isViewPipelineSearchQueryable = true, }) => { const { readWrite: preferencesReadWrite, @@ -134,24 +138,23 @@ export const IndexesToolbar: React.FunctionComponent = ({ ) : ( ); - const isViewPipelineSearchQueryable = - isReadonlyView && collectionStats?.pipeline - ? VIEW_PIPELINE_UTILS.isPipelineSearchQueryable( - collectionStats.pipeline as Document[] - ) - : true; + + const showToolbarButtons = shouldShowIndexesToolbarButtons({ + isReadonlyView, + serverVersion, + isSearchManagementActive, + isViewPipelineSearchQueryable, + hasSearchIndexes, + }); const pipelineNotSearchQueryableDescription = 'Search indexes can only be created on views containing $match stages with the $expr operator, $addFields, or $set'; + return (
- {(!isReadonlyView || - (VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass( - serverVersion - ) && - isSearchManagementActive)) && ( + {showToolbarButtons && (
{showCreateIndexButton && ( @@ -402,27 +405,30 @@ export const CreateIndexButton: React.FunctionComponent< ); }; -const mapState = ({ - namespace, - isWritable, - isReadonlyView, - isSearchIndexesSupported, - description, - serverVersion, - searchIndexes, - indexView, - collectionStats, -}: RootState) => ({ - namespace, - isWritable, - isReadonlyView, - isSearchIndexesSupported, - writeStateDescription: description, - indexView, - serverVersion, - searchIndexes, - collectionStats, -}); +const mapState = (state: RootState) => { + const { isViewPipelineSearchQueryable } = selectIsViewSearchCompatible(state); + const { + namespace, + isWritable, + isReadonlyView, + isSearchIndexesSupported, + description, + serverVersion, + searchIndexes, + indexView, + } = state; + return { + namespace, + isWritable, + isReadonlyView, + isSearchIndexesSupported, + writeStateDescription: description, + indexView, + serverVersion, + hasSearchIndexes: searchIndexes.indexes.length > 0, + isViewPipelineSearchQueryable, + }; +}; const mapDispatch = { onCreateRegularIndexClick: () => createIndexOpened(), diff --git a/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.spec.tsx b/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.spec.tsx index 889e9f77b5d..217ca49ed79 100644 --- a/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.spec.tsx +++ b/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.spec.tsx @@ -180,7 +180,7 @@ describe('SearchIndexesTable Component', function () { expect(openCreateSpy.callCount).to.equal(1); }); - it('renders the zero state with button disabled if there are no indexes and isReadOnlyView with non searchable pipeline', function () { + it('renders the standard indexes incompatible empty state if there are no indexes and isReadOnlyView with non searchable pipeline', function () { const pipelineMock: Document[] = [{ $project: { newField: 'testValue' } }]; const mockCollectionStats = { index_count: 0, @@ -195,15 +195,10 @@ describe('SearchIndexesTable Component', function () { { collectionStats: mockCollectionStats } ); - expect(() => { - screen.getByTestId('search-indexes-list'); - }).to.throw(); - - const button = screen.getByTestId('create-atlas-search-index-button'); - expect(button).to.exist; - expect(button.closest('button')?.getAttribute('aria-disabled')).to.equal( - 'true' - ); + expect(screen.queryByTestId('search-indexes-list')).to.not.exist; + expect(screen.queryByTestId('create-atlas-search-index-button')).to.not + .exist; + expect(screen.getByText('No standard indexes')).to.exist; }); context('renders list with action', function () { diff --git a/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx b/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx index 154934ec016..2d37c02789e 100644 --- a/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx +++ b/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx @@ -37,6 +37,7 @@ import { selectIsViewSearchCompatible } from '../../utils/is-view-search-compati import { useSearchIndexesTable } from './use-search-indexes-table'; import { COLUMNS, COLUMNS_WITH_ACTIONS } from './search-indexes-columns'; +import ViewStandardIndexesIncompatibleEmptyState from '../view-incompatible-components/view-standard-indexes-incompatible-empty-state'; type SearchIndexesTableProps = { namespace: string; @@ -209,6 +210,9 @@ export const SearchIndexesTable: React.FunctionComponent< } if (indexes.length === 0) { + if (isReadonlyView && !isViewPipelineSearchQueryable) { + return ; + } return ( - {!hasNoSearchIndexes && ( + {hasNoSearchIndexes && ( <> Looking for search indexes?
diff --git a/packages/compass-indexes/src/utils/should-show-indexes-toolbar-buttons.spec.ts b/packages/compass-indexes/src/utils/should-show-indexes-toolbar-buttons.spec.ts new file mode 100644 index 00000000000..ce56b8b092c --- /dev/null +++ b/packages/compass-indexes/src/utils/should-show-indexes-toolbar-buttons.spec.ts @@ -0,0 +1,81 @@ +import { expect } from 'chai'; + +import { shouldShowIndexesToolbarButtons } from './should-show-indexes-toolbar-buttons'; + +const baseParams = { + isReadonlyView: false, + serverVersion: '8.1.0', + isSearchManagementActive: true, + isViewPipelineSearchQueryable: true, + hasSearchIndexes: false, +}; + +describe('shouldShowIndexesToolbarButtons', function () { + it('always shows the toolbar for non-views', function () { + expect( + shouldShowIndexesToolbarButtons({ + ...baseParams, + isReadonlyView: false, + // even when nothing search-related applies + isSearchManagementActive: false, + isViewPipelineSearchQueryable: false, + }) + ).to.be.true; + }); + + describe('for a readonly view', function () { + const viewParams = { ...baseParams, isReadonlyView: true }; + + it('hides the toolbar when the server version is below 8.1', function () { + expect( + shouldShowIndexesToolbarButtons({ + ...viewParams, + serverVersion: '8.0.0', + }) + ).to.be.false; + }); + + it('hides the toolbar when search management is not active', function () { + expect( + shouldShowIndexesToolbarButtons({ + ...viewParams, + isSearchManagementActive: false, + }) + ).to.be.false; + }); + + it('shows the toolbar for a search-queryable view', function () { + expect( + shouldShowIndexesToolbarButtons({ + ...viewParams, + isViewPipelineSearchQueryable: true, + }) + ).to.be.true; + }); + + describe('when the pipeline is not search-queryable', function () { + const incompatibleViewParams = { + ...viewParams, + isViewPipelineSearchQueryable: false, + }; + + it('hides the toolbar when there are no existing search indexes', function () { + expect( + shouldShowIndexesToolbarButtons({ + ...incompatibleViewParams, + hasSearchIndexes: false, + }) + ).to.be.false; + }); + + it('shows the toolbar when there are existing search indexes', function () { + expect( + shouldShowIndexesToolbarButtons({ + ...incompatibleViewParams, + hasSearchIndexes: true, + }) + ).to.be.true; + }); + }); + }); +}); diff --git a/packages/compass-indexes/src/utils/should-show-indexes-toolbar-buttons.ts b/packages/compass-indexes/src/utils/should-show-indexes-toolbar-buttons.ts new file mode 100644 index 00000000000..4906aa175bc --- /dev/null +++ b/packages/compass-indexes/src/utils/should-show-indexes-toolbar-buttons.ts @@ -0,0 +1,38 @@ +import { VIEW_PIPELINE_UTILS } from '@mongodb-js/mongodb-constants'; + +/** + * Returns true when the indexes toolbar action buttons (create index, refresh, + * segmented control, etc.) should be shown. + */ +export function shouldShowIndexesToolbarButtons({ + isReadonlyView, + serverVersion, + isSearchManagementActive, + isViewPipelineSearchQueryable, + hasSearchIndexes, +}: { + isReadonlyView: boolean; + serverVersion: string; + isSearchManagementActive: boolean; + isViewPipelineSearchQueryable: boolean; + hasSearchIndexes: boolean; +}): boolean { + // Non-views always show the toolbar. + if (!isReadonlyView) { + return true; + } + + // Views need 8.1+ and search management enabled. + if ( + !VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass( + serverVersion + ) || + !isSearchManagementActive + ) { + return false; + } + + // Search-queryable views, or incompatible views that still have existing + // indexes show toolbar. + return isViewPipelineSearchQueryable || hasSearchIndexes; +}