diff --git a/packages/collection-model/index.d.ts b/packages/collection-model/index.d.ts index 639f1d69c67..5bb533a4632 100644 --- a/packages/collection-model/index.d.ts +++ b/packages/collection-model/index.d.ts @@ -56,6 +56,7 @@ interface Collection { isReadonly: boolean; isTimeSeries: boolean; isClustered: boolean; + isFLE: boolean; sourceName?: string; sourceReadonly?: boolean; sourceViewon?: string; diff --git a/packages/collection-model/lib/collection-properties.js b/packages/collection-model/lib/collection-properties.js index 7751d22e0b5..886dc9ecfec 100644 --- a/packages/collection-model/lib/collection-properties.js +++ b/packages/collection-model/lib/collection-properties.js @@ -2,6 +2,7 @@ const PROPERTIES_COLLATION = 'collation'; const PROPERTIES_TIME_SERIES = 'timeseries'; const PROPERTIES_CAPPED = 'capped'; const PROPERTIES_CLUSTERED = 'clustered'; +const PROPERTIES_FLE2 = 'fle2'; const PROPERTIES_VIEW = 'view'; const PROPERTIES_READ_ONLY = 'read-only'; @@ -45,6 +46,12 @@ function getProperties(coll) { }); } + if (coll.fle2) { + properties.push({ + name: PROPERTIES_FLE2, + }); + } + return properties; } diff --git a/packages/collection-model/lib/model.js b/packages/collection-model/lib/model.js index 6d87a2c35c8..de04845deab 100644 --- a/packages/collection-model/lib/model.js +++ b/packages/collection-model/lib/model.js @@ -99,9 +99,10 @@ function pickCollectionInfo({ collation, pipeline, validation, - clustered + clustered, + fle2, }) { - return { readonly, view_on, collation, pipeline, validation, clustered }; + return { readonly, view_on, collation, pipeline, validation, clustered, fle2 }; } /** @@ -124,6 +125,7 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), { // Normalized values from collectionInfo command readonly: 'boolean', clustered: 'boolean', + fle2: 'boolean', view_on: 'string', collation: 'object', pipeline: 'array', @@ -205,37 +207,32 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), { return getNamespaceInfo(this._id).normal; }, }, - isTimeSeries: { deps: ['type'], fn() { return this.type === 'timeseries'; }, }, - isView: { deps: ['type'], fn() { return this.type === 'view'; }, }, - sourceId: { deps: ['view_on'], fn() { return this.view_on ? `${this.database}.${this.view_on}` : null; }, }, - source: { deps: ['sourceId'], fn() { return this.collection.get(this.sourceId) ?? null; }, }, - properties: { - deps: ['collation', 'type', 'capped', 'clustered', 'readonly'], + deps: ['collation', 'type', 'capped', 'clustered', 'readonly', 'fle2'], fn() { return getProperties(this); }, @@ -285,11 +282,13 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), { } // We don't care if it fails to get stats from server for any reason } + const collectionMetadata = { namespace: this.ns, isReadonly: this.readonly, isTimeSeries: this.isTimeSeries, - isClustered: this.clustered + isClustered: this.clustered, + isFLE: this.fle2, }; if (this.sourceId) { try { diff --git a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx index ae9dc9e54ba..7c1d6426181 100644 --- a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx +++ b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx @@ -19,6 +19,7 @@ describe('CollectionHeader [Component]', function () { isReadonly={false} isTimeSeries={false} isClustered={false} + isFLE={false} sourceName={null} globalAppRegistry={globalAppRegistry} namespace="db.coll" @@ -71,8 +72,9 @@ describe('CollectionHeader [Component]', function () { isReadonly={true} isTimeSeries={false} isClustered={false} - globalAppRegistry={globalAppRegistry} + isFLE={false} sourceName="orig.coll" + globalAppRegistry={globalAppRegistry} namespace="db.coll" selectOrCreateTab={selectOrCreateTabSpy} sourceReadonly={false} @@ -121,6 +123,7 @@ describe('CollectionHeader [Component]', function () { isReadonly={true} isTimeSeries={false} isClustered={false} + isFLE={false} sourceName={null} globalAppRegistry={globalAppRegistry} namespace="db.coll" @@ -157,6 +160,7 @@ describe('CollectionHeader [Component]', function () { isReadonly={false} isTimeSeries={true} isClustered={false} + isFLE={false} sourceName={null} globalAppRegistry={globalAppRegistry} namespace="db.coll" @@ -193,6 +197,7 @@ describe('CollectionHeader [Component]', function () { isReadonly={false} isTimeSeries={false} isClustered={true} + isFLE={false} sourceName={null} globalAppRegistry={globalAppRegistry} namespace="db.coll" @@ -223,6 +228,35 @@ describe('CollectionHeader [Component]', function () { }); }); + context('when the collection is a fle collection', function () { + const globalAppRegistry = new AppRegistry(); + const selectOrCreateTabSpy = spy(); + + beforeEach(function () { + render( + + ); + }); + + afterEach(cleanup); + + it('renders the clustered badge', function () { + expect(screen.getByTestId('collection-badge-fle')).to.exist; + }); + }); + context('when the db name is clicked', function () { it('emits the open event to the app registry', function () { const selectOrCreateTabSpy = spy(); @@ -235,6 +269,7 @@ describe('CollectionHeader [Component]', function () { isReadonly={false} isTimeSeries={false} isClustered={false} + isFLE={false} globalAppRegistry={ { emit: (eventName, dbName) => { diff --git a/packages/compass-collection/src/components/collection-header/collection-header.tsx b/packages/compass-collection/src/components/collection-header/collection-header.tsx index de524f1d81a..94e6a4b5704 100644 --- a/packages/compass-collection/src/components/collection-header/collection-header.tsx +++ b/packages/compass-collection/src/components/collection-header/collection-header.tsx @@ -19,6 +19,7 @@ import ViewBadge from './view-badge'; import CollectionStats from '../collection-stats'; import type { CollectionStatsObject } from '../../modules/stats'; import ClusteredBadge from './clustered-badge'; +import FLEBadge from './fle-badge'; const collectionHeaderStyles = css({ paddingTop: spacing[3], @@ -105,6 +106,7 @@ type CollectionHeaderProps = { isReadonly: boolean; isTimeSeries: boolean; isClustered: boolean; + isFLE: boolean; selectOrCreateTab: (options: any) => any; sourceName: string; sourceReadonly: boolean; @@ -122,6 +124,8 @@ class CollectionHeader extends Component { namespace: this.props.sourceName, isReadonly: this.props.sourceReadonly, isTimeSeries: this.props.isTimeSeries, + isClustered: this.props.isClustered, + isFLE: this.props.isFLE, sourceName: this.props.sourceViewOn, editViewName: this.props.namespace, sourceReadonly: false, @@ -136,6 +140,7 @@ class CollectionHeader extends Component { isReadonly: true, isTimeSeries: this.props.isTimeSeries, isClustered: this.props.isClustered, + isFLE: this.props.isFLE, sourceName: this.props.namespace, editViewName: null, sourceReadonly: this.props.isReadonly, @@ -207,6 +212,7 @@ class CollectionHeader extends Component { {this.props.isReadonly && } {this.props.isTimeSeries && } {this.props.isClustered && } + {this.props.isFLE && } {this.props.isReadonly && this.props.sourceName && } ( + + {/* TODO(COMPASS-5626): use proper name instead of FLE2 */} + +  FLE2 + +); + +export default FLEBadge; diff --git a/packages/compass-collection/src/components/collection/collection.spec.tsx b/packages/compass-collection/src/components/collection/collection.spec.tsx index 8938758e709..28a519e57b7 100644 --- a/packages/compass-collection/src/components/collection/collection.spec.tsx +++ b/packages/compass-collection/src/components/collection/collection.spec.tsx @@ -21,6 +21,7 @@ describe('Collection [Component]', function () { isReadonly={false} isTimeSeries={false} isClustered={false} + isFLE={false} sourceName={null} tabs={[]} views={[]} diff --git a/packages/compass-collection/src/components/collection/collection.tsx b/packages/compass-collection/src/components/collection/collection.tsx index 1049b4a1b54..9d9fa4d70e8 100644 --- a/packages/compass-collection/src/components/collection/collection.tsx +++ b/packages/compass-collection/src/components/collection/collection.tsx @@ -52,6 +52,7 @@ type CollectionProps = { isReadonly: boolean; isTimeSeries: boolean; isClustered: boolean; + isFLE: boolean; editViewName?: string; sourceReadonly: boolean; sourceViewOn?: string; @@ -83,6 +84,7 @@ const Collection: React.FunctionComponent = ({ isReadonly, isTimeSeries, isClustered, + isFLE, stats, editViewName, sourceReadonly, @@ -150,6 +152,7 @@ const Collection: React.FunctionComponent = ({ isReadonly={isReadonly} isTimeSeries={isTimeSeries} isClustered={isClustered} + isFLE={isFLE} editViewName={editViewName} sourceReadonly={sourceReadonly} sourceViewOn={sourceViewOn} diff --git a/packages/compass-collection/src/components/workspace/workspace.tsx b/packages/compass-collection/src/components/workspace/workspace.tsx index 1ae0c92e047..baf3e284aa4 100644 --- a/packages/compass-collection/src/components/workspace/workspace.tsx +++ b/packages/compass-collection/src/components/workspace/workspace.tsx @@ -73,6 +73,8 @@ const DEFAULT_NEW_TAB = { namespace: '', isReadonly: false, isTimeSeries: false, + isClustered: false, + isFLE: false, sourceName: '', }; @@ -158,6 +160,7 @@ class Workspace extends PureComponent { isReadonly: activeTab.isReadonly, isTimeSeries: activeTab.isTimeSeries, isClustered: activeTab.isClustered, + isFLE: activeTab.isFLE, sourceName: activeTab.sourceName, editViewName: activeTab.editViewName, sourceReadonly: activeTab.sourceReadonly, @@ -229,6 +232,7 @@ class Workspace extends PureComponent { isReadonly={tab.isReadonly} isTimeSeries={tab.isTimeSeries} isClustered={tab.isClustered} + isFLE={tab.isFLE} sourceName={tab.sourceName} editViewName={tab.editViewName} sourceReadonly={tab.sourceReadonly} diff --git a/packages/compass-collection/src/modules/tabs.ts b/packages/compass-collection/src/modules/tabs.ts index 9988f19ccce..3beb0e9a9c8 100644 --- a/packages/compass-collection/src/modules/tabs.ts +++ b/packages/compass-collection/src/modules/tabs.ts @@ -68,6 +68,7 @@ export interface WorkspaceTabObject { isReadonly: boolean; isTimeSeries: boolean; isClustered: boolean; + isFLE: boolean; tabs: string[]; views: JSX.Element[]; subtab: WorkspaceTabObject; @@ -125,6 +126,7 @@ const doSelectNamespace = (state: any, action: AnyAction) => { isReadonly: action.isReadonly, isTimeSeries: action.isTimeSeries, isClustered: action.isClustered, + isFLE: action.isFLE, tabs: action.context.tabs, views: action.context.views, subtab: action.context.subtab, @@ -174,6 +176,7 @@ const doCreateTab = (state: any, action: AnyAction) => { isReadonly: action.isReadonly, isTimeSeries: action.isTimeSeries, isClustered: action.isClustered, + isFLE: action.isFLE, tabs: action.context.tabs, views: action.context.views, subtab: action.context.subtab, @@ -386,6 +389,7 @@ export const createTab = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, context, @@ -400,6 +404,7 @@ export const createTab = ({ isReadonly: !!isReadonly, isTimeSeries: !!isTimeSeries, isClustered: !!isClustered, + isFLE: !!isFLE, sourceName, editViewName, context, @@ -417,6 +422,7 @@ export const createTab = ({ * @param {Boolean} isReadonly - Is the collection readonly? * @param {Boolean} isTimeSeries - Is the collection time-series? * @param {Boolean} isClustered - Is the collection clustered? + * @param {Boolean} isFLE - Is the collection FLE? * @param {String} sourceName - The source namespace. * @param {String} editViewName - The name of the view we are editing. * @param {Object} context - The tab context. @@ -431,6 +437,7 @@ export const selectNamespace = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, context, @@ -442,12 +449,13 @@ export const selectNamespace = ({ namespace, isReadonly: !!isReadonly, isTimeSeries, + isClustered, + isFLE, sourceName, editViewName, context, sourceReadonly, sourceViewOn, - isClustered, }); /** @@ -590,6 +598,7 @@ export const selectOrCreateTab = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, sourceReadonly, @@ -605,6 +614,7 @@ export const selectOrCreateTab = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, sourceReadonly, @@ -626,6 +636,7 @@ export const selectOrCreateTab = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, sourceReadonly, @@ -649,6 +660,7 @@ export const createNewTab = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, sourceReadonly, @@ -666,6 +678,7 @@ export const createNewTab = ({ isDataLake: state.isDataLake, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, sourcePipeline, @@ -679,6 +692,7 @@ export const createNewTab = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, context, @@ -703,6 +717,7 @@ export const replaceTabContent = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, sourceReadonly, @@ -718,6 +733,7 @@ export const replaceTabContent = ({ isDataLake: state.isDataLake, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, sourcePipeline, @@ -729,6 +745,7 @@ export const replaceTabContent = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, editViewName, context, diff --git a/packages/compass-collection/src/stores/context.tsx b/packages/compass-collection/src/stores/context.tsx index fe6c4293096..159ad7df36d 100644 --- a/packages/compass-collection/src/stores/context.tsx +++ b/packages/compass-collection/src/stores/context.tsx @@ -64,6 +64,7 @@ type ContextProps = { isReadonly?: boolean; isTimeSeries?: boolean; isClustered?: boolean; + isFLE?: boolean; actions?: any; allowWrites?: boolean; sourceName?: string; @@ -107,6 +108,7 @@ const setupStore = ({ isReadonly, isTimeSeries, isClustered, + isFLE, actions, allowWrites, sourceName, @@ -117,22 +119,23 @@ const setupStore = ({ connectionString, }: ContextProps) => { const store = role.configureStore({ - localAppRegistry: localAppRegistry, - globalAppRegistry: globalAppRegistry, + localAppRegistry, + globalAppRegistry, dataProvider: { error: dataService?.error, dataProvider: dataService?.dataService, }, - namespace: namespace, - serverVersion: serverVersion, - isReadonly: isReadonly, + namespace, + serverVersion, + isReadonly, isTimeSeries, isClustered, + isFLE, actions: actions, - allowWrites: allowWrites, - sourceName: sourceName, - editViewName: editViewName, - sourcePipeline: sourcePipeline, + allowWrites, + sourceName, + editViewName, + sourcePipeline, query, aggregation, connectionString, @@ -155,6 +158,7 @@ const setupStore = ({ * @property {Boolean} options.isReadonly - If the collection is a readonly view. * @property {Boolean} options.isTimeSeries - If the collection is a time-series collection. * @property {Boolean} options.isClustered - If the collection is a clustered index collection. + * @property {Boolean} options.isFLE - If the collection is a FLE collection. * @property {Boolean} options.allowWrites - If writes are allowed. * @property {String} options.key - The plugin key. * @@ -170,6 +174,7 @@ const setupPlugin = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, allowWrites, connectionString, @@ -186,6 +191,7 @@ const setupPlugin = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, actions, allowWrites, @@ -212,6 +218,7 @@ const setupPlugin = ({ * @property {Boolean} options.isReadonly - If the collection is a readonly view. * @property {Boolean} options.isTimeSeries - If the collection is a time-series. * @property {Boolean} options.isClustered - If the collection is a time-series. + * @property {Boolean} options.isFLE - If the collection is a FLE collection. * @property {Boolean} options.allowWrites - If we allow writes. * * @returns {Array} The components. @@ -225,6 +232,7 @@ const setupScopedModals = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, allowWrites, connectionString, @@ -242,6 +250,7 @@ const setupScopedModals = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, allowWrites, connectionString, @@ -272,6 +281,7 @@ const createContext = ({ isReadonly, isTimeSeries, isClustered, + isFLE, isDataLake, sourceName, editViewName, @@ -317,6 +327,7 @@ const createContext = ({ isReadonly, isTimeSeries, isClustered, + isFLE, actions: queryBarActions, allowWrites: !isDataLake, query, @@ -338,6 +349,7 @@ const createContext = ({ isReadonly, isTimeSeries, isClustered, + isFLE, actions, allowWrites: !isDataLake, sourceName, @@ -393,6 +405,7 @@ const createContext = ({ isReadonly, isTimeSeries, isClustered, + isFLE, sourceName, allowWrites: !isDataLake, connectionString: getCurrentlyConnectedUri(state.dataService.dataService), diff --git a/packages/compass-collection/src/stores/index.ts b/packages/compass-collection/src/stores/index.ts index ac342646fc5..56833b5aa08 100644 --- a/packages/compass-collection/src/stores/index.ts +++ b/packages/compass-collection/src/stores/index.ts @@ -176,6 +176,7 @@ store.onActivated = (appRegistry: AppRegistry) => { sourceReadonly: metadata.sourceReadonly, isTimeSeries: !!metadata.isTimeSeries, isClustered: !!metadata.isClustered, + isFLE: !!metadata.isFLE, sourceViewOn: metadata.sourceViewOn, sourcePipeline: metadata.sourcePipeline, query: metadata.query, @@ -208,6 +209,7 @@ store.onActivated = (appRegistry: AppRegistry) => { isReadonly: metadata.isReadonly, isTimeSeries: metadata.isTimeSeries, isClustered: metadata.isClustered, + isFLE: metadata.isFLE, sourceName: metadata.sourceName, editViewName: metadata.editViewName, sourceReadonly: metadata.sourceReadonly, diff --git a/packages/compass-crud/src/stores/crud-store.js b/packages/compass-crud/src/stores/crud-store.js index 02c385e0bb8..5be0af8da9e 100644 --- a/packages/compass-crud/src/stores/crud-store.js +++ b/packages/compass-crud/src/stores/crud-store.js @@ -137,7 +137,6 @@ export const setIsReadonly = (store, isReadonly) => { store.onReadonlyChanged(isReadonly); }; - /** * Set the isTimeSeries flag in the store. * @@ -307,7 +306,7 @@ const configureStore = (options = {}) => { * @param {Boolean} isReadonly - If the collection is readonly. */ onReadonlyChanged(isReadonly) { - this.setState({ isReadonly: isReadonly }); + this.setState({ isReadonly }); }, /** @@ -316,7 +315,7 @@ const configureStore = (options = {}) => { * @param {Boolean} isTimeSeries - If the collection is time-series. */ onTimeSeriesChanged(isTimeSeries) { - this.setState({ isTimeSeries: isTimeSeries }); + this.setState({ isTimeSeries }); }, /** diff --git a/packages/compass-sidebar/src/modules/collection.js b/packages/compass-sidebar/src/modules/collection.js index 25de42a8be4..7715c12eced 100644 --- a/packages/compass-sidebar/src/modules/collection.js +++ b/packages/compass-sidebar/src/modules/collection.js @@ -46,27 +46,3 @@ export const getSourceViewOn = (database, source) => { }; export const TIME_SERIES_COLLECTION_TYPE = 'timeseries'; - -/** - * Get the collection metadata to pass to the collection plugin. - * - * @param {Object} collection - The collection object. - * @param {Array} collections - The list of all collections in the db. - * @param {String} database - The database name. - * @param {String} editViewName - The name of the view being edited. - * - * @returns {Object} The collection metadata. - */ -export const collectionMetadata = (collection, collections, database, editViewName) => { - const source = getSource(collection.view_on, collections); - return { - namespace: collection._id, - isReadonly: collection.readonly, - isTimeSeries: collection.type === TIME_SERIES_COLLECTION_TYPE, - sourceName: getSourceName(collection.readonly, database, collection.view_on), - sourceReadonly: source ? source.readonly : false, - sourceViewOn: getSourceViewOn(database, source), - sourcePipeline: collection.pipeline, - editViewName: editViewName - }; -}; diff --git a/packages/compass-sidebar/src/modules/collection.spec.js b/packages/compass-sidebar/src/modules/collection.spec.js index ae7b776736c..e146fae22cd 100644 --- a/packages/compass-sidebar/src/modules/collection.spec.js +++ b/packages/compass-sidebar/src/modules/collection.spec.js @@ -2,7 +2,6 @@ import { getSource, getSourceName, getSourceViewOn, - collectionMetadata } from './collection'; const COLL = { @@ -80,19 +79,4 @@ describe('collection module', () => { }); }); }); - - describe('#collectionMetadata', () => { - it('returns the object to send to the other plugins', () => { - expect(collectionMetadata(VIEW_ON_VIEW, COLLECTIONS, 'db', 'db.testView')).to.deep.equal({ - namespace: 'db.testViewOnView', - isReadonly: true, - isTimeSeries: false, - sourceName: 'db.testView', - sourceReadonly: true, - sourceViewOn: 'db.test', - sourcePipeline: [], - editViewName: 'db.testView' - }); - }); - }); }); diff --git a/packages/data-service/src/instance-detail-helper.ts b/packages/data-service/src/instance-detail-helper.ts index 84906c4fd42..ba6295dabaf 100644 --- a/packages/data-service/src/instance-detail-helper.ts +++ b/packages/data-service/src/instance-detail-helper.ts @@ -71,6 +71,7 @@ type CollectionDetails = { clustered: boolean; collation: Document | null; view_on: string | null; + fle2: boolean; pipeline: Document[] | null; validation: { validator: Document; @@ -393,6 +394,7 @@ export function adaptCollectionInfo({ validationAction, validationLevel, clusteredIndex, + encryptedFields, } = options ?? {}; const hasValidation = Boolean( @@ -415,6 +417,7 @@ export function adaptCollectionInfo({ view_on: viewOn ?? null, pipeline: pipeline ?? null, clustered: clusteredIndex ? true : false, + fle2: encryptedFields ? true : false, validation: hasValidation ? { validator, validationAction, validationLevel } : null, diff --git a/packages/databases-collections-list/src/collections.tsx b/packages/databases-collections-list/src/collections.tsx index ef26e358dc8..ccb9afac311 100644 --- a/packages/databases-collections-list/src/collections.tsx +++ b/packages/databases-collections-list/src/collections.tsx @@ -60,6 +60,8 @@ function collectionPropertyToBadge({ return { name, variant: 'darkgray' }; case 'timeseries': return { name, variant: 'darkgray', icon: 'TimeSeries' }; + case 'fle2': + return { name, variant: 'darkgray', icon: 'Key' }; case 'clustered': return { name, variant: 'darkgray' }; case 'readonly':