diff --git a/packages/compass-components/src/components/bson-value.tsx b/packages/compass-components/src/components/bson-value.tsx index f8da5f485e5..e4ad3244411 100644 --- a/packages/compass-components/src/components/bson-value.tsx +++ b/packages/compass-components/src/components/bson-value.tsx @@ -144,7 +144,6 @@ const BinaryValue: React.FunctionComponent> = ({ } if (value.sub_type === Binary.SUBTYPE_UUID) { let uuid: string; - try { // Try to get the pretty hex version of the UUID uuid = value.toUUID().toString(); diff --git a/packages/compass-crud/src/components/readonly-document.tsx b/packages/compass-crud/src/components/readonly-document.tsx index f2cbc395b07..f34085e9f37 100644 --- a/packages/compass-crud/src/components/readonly-document.tsx +++ b/packages/compass-crud/src/components/readonly-document.tsx @@ -132,11 +132,13 @@ class ReadonlyDocument extends React.Component< */ renderElements() { return ( - + <> + + ); } diff --git a/packages/compass-crud/src/stores/crud-store.spec.ts b/packages/compass-crud/src/stores/crud-store.spec.ts index 54000017a64..4b0459595d1 100644 --- a/packages/compass-crud/src/stores/crud-store.spec.ts +++ b/packages/compass-crud/src/stores/crud-store.spec.ts @@ -2296,6 +2296,7 @@ describe('store', function () { }); describe('fetchDocuments', function () { + const track = createNoopTrack(); let findResult: unknown[] = []; let csfleMode = 'disabled'; let find = sinon.stub().callsFake(() => { @@ -2323,7 +2324,7 @@ describe('store', function () { }); it('should call find with $bsonSize projection when mongodb version is >= 4.4, not connected to ADF and csfle is disabled', async function () { - await fetchDocuments(dataService, '5.0.0', false, 'test.test', {}); + await fetchDocuments(dataService, track, '5.0.0', false, 'test.test', {}); expect(find).to.have.been.calledOnce; expect(find.getCall(0)) .to.have.nested.property('args.2.projection') @@ -2334,6 +2335,7 @@ describe('store', function () { findResult = [{ __size: new Int32(42), __doc: { _id: 1 } }]; const docs = await fetchDocuments( dataService, + track, '4.0.0', false, 'test.test', @@ -2345,7 +2347,7 @@ describe('store', function () { }); it('should NOT call find with $bsonSize projection when mongodb version is < 4.4', async function () { - await fetchDocuments(dataService, '4.0.0', false, 'test.test', {}); + await fetchDocuments(dataService, track, '4.0.0', false, 'test.test', {}); expect(find).to.have.been.calledOnce; expect(find.getCall(0)).to.have.nested.property( 'args.2.projection', @@ -2354,7 +2356,7 @@ describe('store', function () { }); it('should NOT call find with $bsonSize projection when connected to ADF', async function () { - await fetchDocuments(dataService, '5.0.0', true, 'test.test', {}); + await fetchDocuments(dataService, track, '5.0.0', true, 'test.test', {}); expect(find).to.have.been.calledOnce; expect(find.getCall(0)).to.have.nested.property( 'args.2.projection', @@ -2364,7 +2366,7 @@ describe('store', function () { it('should NOT call find with $bsonSize projection when csfle is enabled', async function () { csfleMode = 'enabled'; - await fetchDocuments(dataService, '5.0.0', false, 'test.test', {}); + await fetchDocuments(dataService, track, '5.0.0', false, 'test.test', {}); expect(find).to.have.been.calledOnce; expect(find.getCall(0)).to.have.nested.property( 'args.2.projection', @@ -2375,6 +2377,7 @@ describe('store', function () { it('should keep user projection when provided', async function () { await fetchDocuments( dataService, + track, '5.0.0', false, 'test.test', @@ -2399,6 +2402,7 @@ describe('store', function () { const docs = await fetchDocuments( dataService, + track, '5.0.0', false, 'test.test', @@ -2419,7 +2423,14 @@ describe('store', function () { find = sinon.stub().rejects(new TypeError('🤷‍♂️')); try { - await fetchDocuments(dataService, '5.0.0', false, 'test.test', {}); + await fetchDocuments( + dataService, + track, + '5.0.0', + false, + 'test.test', + {} + ); expect.fail('Expected fetchDocuments to fail with error'); } catch (err) { expect(find).to.have.been.calledOnce; @@ -2431,7 +2442,14 @@ describe('store', function () { find = sinon.stub().rejects(new MongoServerError('Nope')); try { - await fetchDocuments(dataService, '3.0.0', true, 'test.test', {}); + await fetchDocuments( + dataService, + track, + '3.0.0', + true, + 'test.test', + {} + ); expect.fail('Expected fetchDocuments to fail with error'); } catch (err) { expect(find).to.have.been.calledOnce; diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 3dee965b3b7..78ecd786d3b 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -112,11 +112,13 @@ const INITIAL_BULK_UPDATE_TEXT = `{ export const fetchDocuments: ( dataService: DataService, + track: TrackFunction, serverVersion: string, isDataLake: boolean, ...args: Parameters ) => Promise = async ( dataService: DataService, + track: TrackFunction, serverVersion, isDataLake, ns, @@ -145,17 +147,31 @@ export const fetchDocuments: ( }; try { - return ( + let uuidSubtype3Count = 0; + let uuidSubtype4Count = 0; + const docs = ( await dataService.find(ns, filter, modifiedOptions, executionOptions) ).map((doc) => { const { __doc, __size, ...rest } = doc; + let hadronDoc: HadronDocument; if (__doc && __size && Object.keys(rest).length === 0) { - const hadronDoc = new HadronDocument(__doc); + hadronDoc = new HadronDocument(__doc); hadronDoc.size = Number(__size); - return hadronDoc; + } else { + hadronDoc = new HadronDocument(doc); } - return new HadronDocument(doc); + const { subtype3Count, subtype4Count } = hadronDoc.findUUIDs(); + uuidSubtype3Count += subtype3Count; + uuidSubtype4Count += subtype4Count; + return hadronDoc; }); + if (uuidSubtype3Count > 0) { + track('UUID Encountered', { subtype: 3, count: uuidSubtype3Count }); + } + if (uuidSubtype4Count > 0) { + track('UUID Encountered', { subtype: 4, count: uuidSubtype4Count }); + } + return docs; } catch (err) { // We are handling all the cases where the size calculating projection might // not work, but just in case we run into some other environment or use-case @@ -896,6 +912,7 @@ class CrudStoreImpl try { documents = await fetchDocuments( this.dataService, + this.track, this.state.version, this.state.isDataLake, ns, @@ -1733,6 +1750,7 @@ class CrudStoreImpl ), fetchDocuments( this.dataService, + this.track, this.state.version, this.state.isDataLake, ns, diff --git a/packages/compass-telemetry/src/telemetry-events.ts b/packages/compass-telemetry/src/telemetry-events.ts index 3a5182ac913..20133b20808 100644 --- a/packages/compass-telemetry/src/telemetry-events.ts +++ b/packages/compass-telemetry/src/telemetry-events.ts @@ -2694,6 +2694,14 @@ type CreateIndexButtonClickedEvent = CommonEvent<{ }; }>; +type UUIDEncounteredEvent = CommonEvent<{ + name: 'UUID Encountered'; + payload: { + subtype: 3 | 4; + count: number; + }; +}>; + export type TelemetryEvent = | AggregationCanceledEvent | AggregationCopiedEvent @@ -2816,4 +2824,5 @@ export type TelemetryEvent = | CumulativeLayoutShiftEvent | TimeToFirstByteEvent | ExperimentViewedEvent - | CreateIndexButtonClickedEvent; + | CreateIndexButtonClickedEvent + | UUIDEncounteredEvent; diff --git a/packages/hadron-document/src/document.ts b/packages/hadron-document/src/document.ts index 76068b3d6de..50899a7c6fb 100644 --- a/packages/hadron-document/src/document.ts +++ b/packages/hadron-document/src/document.ts @@ -12,7 +12,7 @@ import type { BSONArray, BSONObject, BSONValue } from './utils'; import { objectToIdiomaticEJSON } from './utils'; import type { HadronEJSONOptions } from './utils'; import { DocumentEvents } from '.'; -import type { MongoServerError } from 'mongodb'; +import type { Binary, MongoServerError } from 'mongodb'; /** * The event constant. @@ -450,6 +450,30 @@ export class Document extends EventEmitter { }, 0); } + findUUIDs() { + let subtype4Count = 0; + let subtype3Count = 0; + for (const element of this.elements) { + if (element.currentType === 'Binary') { + if ((element.value as Binary).sub_type === 4) { + subtype4Count++; + } + if ((element.value as Binary).sub_type === 3) { + subtype3Count++; + } + } else if ( + element.currentType === 'Object' || + element.currentType === 'Array' + ) { + const { subtype3Count: sub3, subtype4Count: sub4 } = + element.findUUIDs(); + subtype3Count += sub3; + subtype4Count += sub4; + } + } + return { subtype3Count, subtype4Count }; + } + startEditing(elementId?: string, field?: 'key' | 'value' | 'type'): void { if (!this.editing) { this.editing = true; diff --git a/packages/hadron-document/src/element.ts b/packages/hadron-document/src/element.ts index 9898ed76bc6..d89ca86876d 100644 --- a/packages/hadron-document/src/element.ts +++ b/packages/hadron-document/src/element.ts @@ -10,7 +10,7 @@ import DateEditor from './editor/date'; import Events from './element-events'; import type Document from './document'; import type { TypeCastTypes } from 'hadron-type-checker'; -import type { ObjectId } from 'bson'; +import type { Binary, ObjectId } from 'bson'; import type { BSONArray, BSONObject, BSONValue } from './utils'; import { getDefaultValueForType } from './utils'; import { DocumentEvents, ElementEvents } from '.'; @@ -879,6 +879,35 @@ export class Element extends EventEmitter { ); } + findUUIDs(): { subtype4Count: number; subtype3Count: number } { + let subtype4Count = 0; + let subtype3Count = 0; + + if (!this.elements) { + return { subtype4Count, subtype3Count }; + } + for (const element of this.elements) { + if (element.currentType === 'Binary') { + if ((element.currentValue as Binary).sub_type === 4) { + subtype4Count++; + } + if ((element.currentValue as Binary).sub_type === 3) { + subtype3Count++; + } + } else if ( + element.currentType === 'Object' || + element.currentType === 'Array' + ) { + const { subtype3Count: sub3, subtype4Count: sub4 } = + element.findUUIDs(); + subtype3Count += sub3; + subtype4Count += sub4; + } + } + + return { subtype4Count, subtype3Count }; + } + private emitVisibleElementsChanged(targetElement: Element | Document = this) { if (targetElement.isRoot()) { targetElement.emit(DocumentEvents.VisibleElementsChanged, targetElement);