diff --git a/packages/compass-crud/src/index.ts b/packages/compass-crud/src/index.ts index 36ca93f22da..42cb8989995 100644 --- a/packages/compass-crud/src/index.ts +++ b/packages/compass-crud/src/index.ts @@ -83,3 +83,6 @@ export type { DocumentListViewProps } from './components/document-list-view'; export { default as DocumentListView } from './components/document-list-view'; export type { DocumentJsonViewProps } from './components/document-json-view'; export { default as DocumentJsonView } from './components/document-json-view'; + +// Export utility functions for use by other packages +export { fetchShardingKeys } from './utils'; diff --git a/packages/compass-indexes/src/components/regular-indexes-table/property-field.tsx b/packages/compass-indexes/src/components/regular-indexes-table/property-field.tsx index 9c36e1ba7ad..f4baaf35fdb 100644 --- a/packages/compass-indexes/src/components/regular-indexes-table/property-field.tsx +++ b/packages/compass-indexes/src/components/regular-indexes-table/property-field.tsx @@ -63,14 +63,17 @@ type PropertyFieldProps = { cardinality?: RegularIndex['cardinality']; extra?: RegularIndex['extra']; properties: RegularIndex['properties']; + isShardKeyIndex?: boolean; }; const HIDDEN_INDEX_TEXT = 'HIDDEN'; +const SHARD_KEY_INDEX_TEXT = 'SHARD KEY'; const PropertyField: React.FunctionComponent = ({ extra, properties, cardinality, + isShardKeyIndex, }) => { return (
@@ -85,6 +88,12 @@ const PropertyField: React.FunctionComponent = ({ /> ); })} + {isShardKeyIndex && ( + + )} {cardinality === 'compound' && ( ), status: , diff --git a/packages/compass-indexes/src/modules/regular-indexes.ts b/packages/compass-indexes/src/modules/regular-indexes.ts index 661ea04d5c4..1bb6926a80e 100644 --- a/packages/compass-indexes/src/modules/regular-indexes.ts +++ b/packages/compass-indexes/src/modules/regular-indexes.ts @@ -1,10 +1,11 @@ import { isEqual, pick } from 'lodash'; -import type { IndexDefinition } from 'mongodb-data-service'; +import type { IndexDefinition, DataService } from 'mongodb-data-service'; import type { AnyAction } from 'redux'; import { openToast, showConfirmation as showConfirmationModal, } from '@mongodb-js/compass-components'; +import { fetchShardingKeys } from '@mongodb-js/compass-crud'; import { FetchStatuses, NOT_FETCHABLE_STATUSES } from '../utils/fetch-status'; import type { FetchStatus } from '../utils/fetch-status'; @@ -37,7 +38,9 @@ export type RegularIndex = Partial & | 'relativeSize' | 'usageCount' | 'buildProgress' - >; + > & { + isShardKeyIndex?: boolean; + }; export type InProgressIndex = Pick & { id: string; @@ -379,6 +382,28 @@ function pickCollectionStatFields(state: RootState) { ); } +/** + * Determines if an index is built on shard key fields by comparing + * the index fields with the shard key fields. + */ +function isIndexOnShardKey( + indexFields: IndexDefinition['fields'], + shardKey: Record +): boolean { + if (!shardKey || Object.keys(shardKey).length === 0) { + return false; + } + + const shardKeyFields = Object.keys(shardKey); + const indexFieldNames = indexFields.map((field) => field.field); + + // Check if index starts with shard key fields (prefix match) + // This covers both exact shard key indexes and compound indexes that include the shard key + return shardKeyFields.every( + (field, index) => indexFieldNames[index] === field + ); +} + const fetchIndexes = ( reason: FetchReason ): IndexesThunkAction, FetchIndexesActions> => { @@ -426,10 +451,25 @@ const fetchIndexes = ( shouldFetchRollingIndexes ? rollingIndexesService.listRollingIndexes(namespace) : undefined, - ] as [Promise, Promise | undefined]; - const [indexes, rollingIndexes] = await Promise.all(promises); + // Fetch shard key information to identify shard key indexes + fetchShardingKeys(dataService as DataService, namespace, { + signal: new AbortController().signal, + }).catch(() => ({})), + ] as [ + Promise, + Promise | undefined, + Promise> + ]; + const [indexes, rollingIndexes, shardKey] = await Promise.all(promises); + + // Mark indexes that are built on shard key fields + const indexesWithShardKeyInfo = indexes.map((index) => ({ + ...index, + isShardKeyIndex: isIndexOnShardKey(index.fields, shardKey), + })); + const indexesBefore = pickCollectionStatFields(getState()); - dispatch(fetchIndexesSucceeded(indexes, rollingIndexes)); + dispatch(fetchIndexesSucceeded(indexesWithShardKeyInfo, rollingIndexes)); const indexesAfter = pickCollectionStatFields(getState()); if ( reason !== FetchReasons.INITIAL_FETCH && diff --git a/packages/compass-indexes/src/stores/store.ts b/packages/compass-indexes/src/stores/store.ts index 5e547467495..4b87040baa3 100644 --- a/packages/compass-indexes/src/stores/store.ts +++ b/packages/compass-indexes/src/stores/store.ts @@ -50,7 +50,10 @@ export type IndexesDataServiceProps = | 'collectionStats' | 'collectionInfo' | 'listCollections' - | 'isListSearchIndexesSupported'; + | 'isListSearchIndexesSupported' + // Required for shard key detection + | 'find' + | 'isCancelError'; export type IndexesDataService = Pick; export type IndexesPluginServices = { diff --git a/packages/compass-indexes/src/utils/index-link-helper.ts b/packages/compass-indexes/src/utils/index-link-helper.ts index 50c896dce7c..4f256203542 100644 --- a/packages/compass-indexes/src/utils/index-link-helper.ts +++ b/packages/compass-indexes/src/utils/index-link-helper.ts @@ -22,6 +22,7 @@ const HELP_URLS = { 'https://docs.mongodb.com/master/reference/bson-type-comparison-order/#collation', COLLATION_REF: 'https://docs.mongodb.com/master/reference/collation', HIDDEN: 'https://www.mongodb.com/docs/manual/core/index-hidden/', + 'SHARD-KEY': 'https://www.mongodb.com/docs/manual/core/sharding-shard-key/', UNKNOWN: null, }; diff --git a/packages/compass-indexes/test/setup-store.ts b/packages/compass-indexes/test/setup-store.ts index e0034988b0b..7d3aff1a7db 100644 --- a/packages/compass-indexes/test/setup-store.ts +++ b/packages/compass-indexes/test/setup-store.ts @@ -90,6 +90,15 @@ const NOOP_DATA_PROVIDER: IndexesDataService = { sample(namespace: string) { return Promise.resolve([]); }, + // Required for shard key detection + // eslint-disable-next-line @typescript-eslint/no-unused-vars + find(ns: string, filter: unknown, options: unknown) { + return Promise.resolve([]); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isCancelError(error: unknown) { + return false; + }, }; class FakeInstance extends EventEmitter {