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 9bac366ab8e..41c5634838b 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 @@ -113,27 +113,49 @@ describe('IndexesToolbar Component', function () { }); }); }); - - it('should not render a warning', function () { - expect(screen.queryByText('Readonly views may not contain indexes')).to - .not.exist; - }); }); describe('when it is a readonly view', function () { - beforeEach(function () { - renderIndexesToolbar({ - isReadonlyView: true, + describe('and server version is < 8.1+', function () { + beforeEach(function () { + renderIndexesToolbar({ + isReadonlyView: true, + indexView: 'search-indexes', + }); }); - }); - it('should not render the create index button', function () { - expect(screen.queryByText('Create Index')).to.not.exist; + it('should not render the create index button', function () { + expect(screen.queryByText('Create Index')).to.not.exist; + }); + + it('should not render the create search index button', function () { + expect(screen.queryByText('Create Search Index')).to.not.exist; + }); + + it('should not render the refresh button', function () { + expect(screen.queryByText('Refresh')).to.not.exist; + }); }); + describe('and server version is > 8.1+', function () { + beforeEach(function () { + renderIndexesToolbar({ + isReadonlyView: true, + serverVersion: '8.1.0', + indexView: 'search-indexes', + }); + }); + + it('should not render the create index button', function () { + expect(screen.queryByText('Create Index')).to.not.exist; + }); - it('should render a warning', function () { - expect(screen.getByText('Readonly views may not contain indexes.')).to.be - .visible; + it('should render the create search index button <8.1', function () { + expect(screen.getByText('Create Search Index')).to.be.visible; + }); + + it('should not render the refresh button', function () { + expect(screen.queryByText('Refresh')).to.be.visible; + }); }); }); @@ -332,5 +354,27 @@ describe('IndexesToolbar Component', function () { expect(segmentControl.closest('button')).to.have.attr('disabled'); expect(onChangeViewCallback).to.not.have.been.calledOnce; }); + + describe('and readonly view >8.1', function () { + beforeEach(function () { + renderIndexesToolbar({ + isReadonlyView: true, + onIndexViewChanged: onChangeViewCallback, + serverVersion: '8.1.0', + }); + }); + + it('it renders tabs with Indexes disabled and automatically selects Search Indexes', function () { + const indexesTab = screen.getByText('Indexes'); + expect(indexesTab).to.be.visible; + expect(indexesTab.closest('button')).to.have.attr('disabled'); + + expect(screen.getByText('Search Indexes')).to.be.visible; + expect(onChangeViewCallback).to.have.been.calledOnce; + expect(onChangeViewCallback.firstCall.args[0]).to.equal( + 'search-indexes' + ); + }); + }); }); }); 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 0989e4edee1..90d7e674a60 100644 --- a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx +++ b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx @@ -1,24 +1,23 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { connect } from 'react-redux'; import { - withPreferences, usePreference, + withPreferences, } from 'compass-preferences-model/provider'; import { Button, - ErrorSummary, - Tooltip, - WarningSummary, - Link, css, - spacing, + DropdownMenuButton, + ErrorSummary, Icon, - SpinLoader, - SignalPopover, + Link, PerformanceSignals, - DropdownMenuButton, SegmentedControl, SegmentedControlOption, + SignalPopover, + spacing, + SpinLoader, + Tooltip, } from '@mongodb-js/compass-components'; import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import semver from 'semver'; @@ -107,9 +106,15 @@ export const IndexesToolbar: React.FunctionComponent = ({ readOnly, // preferences readOnly. }) => { const isSearchManagementActive = usePreference('enableAtlasSearchIndexes'); + const mongoDBMajorVersion = parseFloat( + serverVersion.split('.').slice(0, 2).join('.') + ); const { atlasMetadata } = useConnectionInfo(); const showInsights = usePreference('showInsights') && !errorMessage; - const showCreateIndexButton = !isReadonlyView && !readOnly && !errorMessage; + const showCreateIndexButton = + (!isReadonlyView || mongoDBMajorVersion > 8.0) && + !readOnly && + !errorMessage; const refreshButtonIcon = isRefreshing ? (
@@ -118,12 +123,19 @@ export const IndexesToolbar: React.FunctionComponent = ({ ); + useEffect(() => { + // If the view is readonly, set the default tab to 'search-indexes' + if (isReadonlyView && indexView !== 'search-indexes') { + onIndexViewChanged('search-indexes'); // Update redux state + } + }, [indexView, isReadonlyView, onIndexViewChanged]); + return (
- {!isReadonlyView && ( + {(!isReadonlyView || mongoDBMajorVersion > 8.0) && (
{showCreateIndexButton && ( @@ -139,7 +151,9 @@ export const IndexesToolbar: React.FunctionComponent = ({ isWritable={isWritable} onCreateRegularIndexClick={onCreateRegularIndexClick} onCreateSearchIndexClick={onCreateSearchIndexClick} - > + isReadonlyView={isReadonlyView} + indexView={indexView} + />
} > @@ -173,6 +187,7 @@ export const IndexesToolbar: React.FunctionComponent = ({ signals={PerformanceSignals.get('too-many-indexes')} /> )} + {isSearchManagementActive && ( = ({ value={indexView} data-testid="indexes-segment-controls" > - - Indexes - - {!isSearchIndexesSupported && ( + {isReadonlyView && ( + + Indexes + + } + > + Readonly views may not contain standard indexes. + + )} + {!isReadonlyView && ( + + Indexes + + )} + {!isSearchIndexesSupported && !isReadonlyView && ( = ({ )} )} - {isSearchIndexesSupported && ( + {(isSearchIndexesSupported || isReadonlyView) && ( = ({
)} - {isReadonlyView ? ( - - ) : ( - !!errorMessage && ( - - ) + {!!errorMessage && ( + )}
); @@ -259,6 +289,8 @@ type CreateIndexButtonProps = { isWritable: boolean; onCreateRegularIndexClick: () => void; onCreateSearchIndexClick: () => void; + isReadonlyView: boolean; + indexView: IndexView; }; type CreateIndexActions = 'createRegularIndex' | 'createSearchIndex'; @@ -271,6 +303,8 @@ export const CreateIndexButton: React.FunctionComponent< isWritable, onCreateRegularIndexClick, onCreateSearchIndexClick, + isReadonlyView, + indexView, }) => { const onActionDispatch = useCallback( (action: CreateIndexActions) => { @@ -284,7 +318,23 @@ export const CreateIndexButton: React.FunctionComponent< [onCreateRegularIndexClick, onCreateSearchIndexClick] ); - if (isSearchIndexesSupported && isSearchManagementActive) { + if (isReadonlyView && isSearchManagementActive) { + if (indexView === 'search-indexes') { + return ( + + ); + } + + return null; + } + if (!isReadonlyView && isSearchIndexesSupported && isSearchManagementActive) { return ( = { - indexView: props.indexView ?? 'regular-indexes', - regularIndexes: { - ...state.regularIndexes, - ...props.regularIndexes, - }, - searchIndexes: { - ...state.searchIndexes, - ...props.searchIndexes, - }, - }; - - Object.assign(store.getState(), allProps); + const newState = { ...state, ...props }; + Object.assign(store.getState(), newState); } render( @@ -332,5 +320,69 @@ describe('Indexes Component', function () { expect(getSearchIndexesStub.callCount).to.equal(2); }); + + it('renders search indexes list if isReadonlyView >8.0 and has indexes', async function () { + const getSearchIndexesStub = sinon.stub().resolves(searchIndexes); + const dataProvider = { + getSearchIndexes: getSearchIndexesStub, + }; + await renderIndexes(undefined, dataProvider, { + indexView: 'search-indexes', + isReadonlyView: true, + serverVersion: '8.1.0', + }); + + await waitFor(() => { + expect(screen.getByTestId('search-indexes-list')).to.exist; + }); + }); + + it('renders correct empty state if isReadonlyView >8.0 and has no indexes', async function () { + const getSearchIndexesStub = sinon.stub().resolves([]); + const dataProvider = { + getSearchIndexes: getSearchIndexesStub, + }; + await renderIndexes(undefined, dataProvider, { + indexView: 'search-indexes', + isReadonlyView: true, + serverVersion: '8.1.0', + }); + + expect(screen.getByText('No search indexes yet')).to.be.visible; + expect(screen.getByText('Create Atlas Search Index')).to.be.visible; + }); + + it('renders correct empty state if isReadonlyView 8.0 and has no indexes', async function () { + const getSearchIndexesStub = sinon.stub().resolves([]); + const dataProvider = { + getSearchIndexes: getSearchIndexesStub, + }; + await renderIndexes(undefined, dataProvider, { + indexView: 'search-indexes', + isReadonlyView: true, + serverVersion: '8.0.0', + }); + + expect(screen.queryByTestId('upgrade-cluster-banner-8.0')).to.exist; + expect(screen.queryByText('No standard indexes')).to.exist; + expect(screen.queryByText('Create Atlas Search Index')).to.not.exist; + }); + it('renders correct empty state if isReadonlyView <8.0 and has no indexes', async function () { + const getSearchIndexesStub = sinon.stub().resolves([]); + const dataProvider = { + getSearchIndexes: getSearchIndexesStub, + }; + await renderIndexes(undefined, dataProvider, { + indexView: 'search-indexes', + isReadonlyView: true, + serverVersion: '7.0.0', + }); + + expect( + screen.queryByTestId('upgrade-cluster-banner-less-than-8.0') + ).to.exist; + expect(screen.queryByText('No standard indexes')).to.exist; + expect(screen.queryByText('Create Atlas Search Index')).to.not.exist; + }); }); }); diff --git a/packages/compass-indexes/src/components/indexes/indexes.tsx b/packages/compass-indexes/src/components/indexes/indexes.tsx index 0e512e13ce2..f27a93baec3 100644 --- a/packages/compass-indexes/src/components/indexes/indexes.tsx +++ b/packages/compass-indexes/src/components/indexes/indexes.tsx @@ -2,12 +2,13 @@ import React from 'react'; import { connect } from 'react-redux'; import { Banner, - Body, Link, WorkspaceContainer, css, spacing, usePersistedState, + EmptyContent, + Body, } from '@mongodb-js/compass-components'; import IndexesToolbar from '../indexes-toolbar/indexes-toolbar'; @@ -29,6 +30,8 @@ import { usePreference } from 'compass-preferences-model/provider'; import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { getAtlasSearchIndexesLink } from '../../utils/atlas-search-indexes-link'; import CreateIndexModal from '../create-index-modal/create-index-modal'; +import { ZeroGraphic } from '../search-indexes-table/zero-graphic'; +import { ViewVersionIncompatibleBanner } from '../view-version-incompatible-banners/view-version-incompatible-banners'; // This constant is used as a trigger to show an insight whenever number of // indexes in a collection is more than what is specified here. @@ -48,6 +51,34 @@ const linkTitle = 'Search and Vector Search.'; const DISMISSED_SEARCH_INDEXES_BANNER_LOCAL_STORAGE_KEY = 'mongodb_compass_dismissedSearchIndexesBanner' as const; +const ViewVersionIncompatibleEmptyState = ({ + mongoDBMajorVersion, + enableAtlasSearchIndexes, +}: { + mongoDBMajorVersion: number; + enableAtlasSearchIndexes: boolean; +}) => { + if (mongoDBMajorVersion > 8.0 && enableAtlasSearchIndexes) { + return null; + } + return ( + + Learn more about views + + } + /> + ); +}; + const AtlasIndexesBanner = ({ namespace, dismissed, @@ -92,6 +123,7 @@ type IndexesProps = { currentIndexesView: IndexView; refreshRegularIndexes: () => void; refreshSearchIndexes: () => void; + serverVersion: string; }; function isRefreshingStatus(status: FetchStatus) { @@ -117,6 +149,7 @@ export function Indexes({ currentIndexesView, refreshRegularIndexes, refreshSearchIndexes, + serverVersion, }: IndexesProps) { const [atlasBannerDismissed, setDismissed] = usePersistedState( DISMISSED_SEARCH_INDEXES_BANNER_LOCAL_STORAGE_KEY, @@ -143,6 +176,10 @@ export function Indexes({ : refreshSearchIndexes; const enableAtlasSearchIndexes = usePreference('enableAtlasSearchIndexes'); + const mongoDBMajorVersion = parseFloat( + serverVersion.split('.').slice(0, 2).join('.') + ); + const { atlasMetadata } = useConnectionInfo(); return (
@@ -162,20 +199,34 @@ export function Indexes({ } >
- {!isReadonlyView && !enableAtlasSearchIndexes && ( - { - setDismissed(true); - }} + {isReadonlyView && ( + )} + {(!isReadonlyView || mongoDBMajorVersion >= 8.0) && + !enableAtlasSearchIndexes && ( + { + setDismissed(true); + }} + /> + )} {!isReadonlyView && currentIndexesView === 'regular-indexes' && ( )} - {!isReadonlyView && currentIndexesView === 'search-indexes' && ( - + {(!isReadonlyView || mongoDBMajorVersion > 8.0) && + currentIndexesView === 'search-indexes' && } + {isReadonlyView && searchIndexes.indexes.length === 0 && ( + )}
@@ -192,12 +243,14 @@ const mapState = ({ regularIndexes, searchIndexes, indexView, + serverVersion, }: RootState) => ({ namespace, isReadonlyView, regularIndexes, searchIndexes, currentIndexesView: indexView, + serverVersion, }); const mapDispatch = { diff --git a/packages/compass-indexes/src/components/view-version-incompatible-banners/view-version-incompatible-banners.tsx b/packages/compass-indexes/src/components/view-version-incompatible-banners/view-version-incompatible-banners.tsx new file mode 100644 index 00000000000..c1895a4edf6 --- /dev/null +++ b/packages/compass-indexes/src/components/view-version-incompatible-banners/view-version-incompatible-banners.tsx @@ -0,0 +1,96 @@ +import { + Banner, + BannerVariant, + Button, + css, +} from '@mongodb-js/compass-components'; +import { getAtlasUpgradeClusterLink } from '../../utils/atlas-upgrade-cluster-link'; +import React from 'react'; +import type { AtlasClusterMetadata } from '@mongodb-js/connection-info'; + +const viewContentStyles = css({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', +}); + +export const ViewVersionIncompatibleBanner = ({ + serverVersion, + mongoDBMajorVersion, + enableAtlasSearchIndexes, + atlasMetadata, +}: { + serverVersion: string; + mongoDBMajorVersion: number; + enableAtlasSearchIndexes: boolean; + atlasMetadata: AtlasClusterMetadata | undefined; +}) => { + const searchIndexOnViewsVersion = enableAtlasSearchIndexes ? '8.1' : '8.0'; + + if ( + mongoDBMajorVersion > 8.0 || + (mongoDBMajorVersion === 8.0 && !enableAtlasSearchIndexes) + ) { + // return if 8.1+ on compass or 8.0+ for data explorer + return null; + } + + if (mongoDBMajorVersion < 8.0) { + // data explorer <8.0 and compass <8.0 + return ( + + Looking for search indexes? +
+
+ + Your MongoDB version is {serverVersion}. Creating and managing + search indexes on views {enableAtlasSearchIndexes && 'in Compass'}{' '} + is supported on MongoDB version {searchIndexOnViewsVersion} or + higher. Upgrade your cluster to create search indexes on views. + + {atlasMetadata && ( + + )} +
+
+ ); + } + + if (mongoDBMajorVersion === 8.0 && enableAtlasSearchIndexes) { + // compass 8.0 + return ( + + Looking for search indexes? +
+
+ + Your MongoDB version is {serverVersion}. Creating and managing + search indexes on views in Compass is supported on MongoDB version{' '} + {searchIndexOnViewsVersion} or higher. Upgrade your cluster or + manage search indexes on views in the Atlas UI. + +
+
+ ); + } + return null; +}; diff --git a/packages/compass-indexes/src/modules/search-indexes.ts b/packages/compass-indexes/src/modules/search-indexes.ts index 7925f05a05c..d4e6c455651 100644 --- a/packages/compass-indexes/src/modules/search-indexes.ts +++ b/packages/compass-indexes/src/modules/search-indexes.ts @@ -602,11 +602,15 @@ const fetchIndexes = ( isReadonlyView, isWritable, namespace, + serverVersion, searchIndexes: { status }, } = getState(); - if (isReadonlyView || !isWritable) { - return; + const mongoDBMajorVersion = parseFloat( + serverVersion.split('.').slice(0, 2).join('.') + ); + if ((isReadonlyView && mongoDBMajorVersion < 8.1) || !isWritable) { + return; // fetch for views 8.1+ } // If we are already fetching indexes, we will wait for that diff --git a/packages/compass-indexes/src/stores/store.ts b/packages/compass-indexes/src/stores/store.ts index dcb4ea04588..e86fcb1a603 100644 --- a/packages/compass-indexes/src/stores/store.ts +++ b/packages/compass-indexes/src/stores/store.ts @@ -156,7 +156,8 @@ export function activateIndexesPlugin( }); void store.dispatch(fetchRegularIndexes()); - if (options.isSearchIndexesSupported) { + + if (options.isSearchIndexesSupported || options.isReadonly) { void store.dispatch(fetchSearchIndexes()); } diff --git a/packages/compass-indexes/src/utils/atlas-upgrade-cluster-link.ts b/packages/compass-indexes/src/utils/atlas-upgrade-cluster-link.ts new file mode 100644 index 00000000000..1fbb95351f8 --- /dev/null +++ b/packages/compass-indexes/src/utils/atlas-upgrade-cluster-link.ts @@ -0,0 +1,7 @@ +export function getAtlasUpgradeClusterLink({ + clusterName, +}: { + clusterName: string; +}) { + return `#/clusters/edit/${encodeURIComponent(clusterName)}`; +}