Skip to content

Commit d2c8d9b

Browse files
v1.39.0
1 parent 8f07e56 commit d2c8d9b

File tree

10 files changed

+109
-40
lines changed

10 files changed

+109
-40
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-components/src/components/document-list/document-actions-group.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import React, { useEffect, useRef, useState } from 'react';
2-
import { css } from '@leafygreen-ui/emotion';
2+
import { css, cx } from '@leafygreen-ui/emotion';
33
import { spacing } from '@leafygreen-ui/tokens';
44
import { Button, Icon } from '../leafygreen';
55
import { Tooltip } from '../tooltip';
6+
import type { Signal } from '../signal-popover';
7+
import { SignalPopover } from '../signal-popover';
68

79
const actionsGroupContainer = css({
810
position: 'absolute',
911
display: 'flex',
12+
alignItems: 'center',
1013
gap: spacing[2],
1114
width: '100%',
1215
top: spacing[2] + spacing[1],
@@ -22,6 +25,24 @@ const actionsGroupItem = css({
2225

2326
const actionsGroupItemSeparator = css({
2427
flex: '1 0 auto',
28+
pointerEvents: 'none',
29+
});
30+
31+
const actionsGroupIdle = css({
32+
'& > button': {
33+
display: 'none',
34+
},
35+
});
36+
37+
const actionsGroupHovered = css({
38+
'& > button': {
39+
display: 'block',
40+
},
41+
});
42+
43+
// Insight icon is always visible, even when action buttons are not
44+
const actionsGroupSignalPopover = css({
45+
display: 'block !important',
2546
});
2647

2748
function useElementParentHoverState<T extends HTMLElement>(
@@ -59,6 +80,7 @@ const DocumentActionsGroup: React.FunctionComponent<
5980
onClone?: () => void;
6081
onRemove?: () => void;
6182
onlyShowOnHover?: boolean;
83+
insights?: Signal | Signal[];
6284
} & (
6385
| { onExpand?: never; expanded?: never }
6486
| { onExpand: () => void; expanded: boolean }
@@ -71,10 +93,13 @@ const DocumentActionsGroup: React.FunctionComponent<
7193
onExpand,
7294
expanded,
7395
onlyShowOnHover = true,
96+
insights,
7497
}) => {
98+
const [signalOpened, setSignalOpened] = useState(false);
7599
const conatinerRef = useRef<HTMLDivElement | null>(null);
76100
const isHovered = useElementParentHoverState(conatinerRef);
77101
const [showCopyButtonTooltip, setShowCopyButtonTooltip] = useState(false);
102+
const isActive = isHovered || signalOpened;
78103

79104
useEffect(() => {
80105
if (showCopyButtonTooltip === true) {
@@ -90,10 +115,10 @@ const DocumentActionsGroup: React.FunctionComponent<
90115
return (
91116
<div
92117
ref={conatinerRef}
93-
className={actionsGroupContainer}
94-
style={{
95-
display: onlyShowOnHover ? (isHovered ? 'flex' : 'none') : 'flex',
96-
}}
118+
className={cx(
119+
actionsGroupContainer,
120+
onlyShowOnHover && (isActive ? actionsGroupHovered : actionsGroupIdle)
121+
)}
97122
>
98123
{onExpand && (
99124
<Button
@@ -113,6 +138,14 @@ const DocumentActionsGroup: React.FunctionComponent<
113138
></Button>
114139
)}
115140
<span className={actionsGroupItemSeparator}></span>
141+
{insights && (
142+
<div className={cx(actionsGroupItem, actionsGroupSignalPopover)}>
143+
<SignalPopover
144+
signals={insights}
145+
onPopoverOpenChange={setSignalOpened}
146+
></SignalPopover>
147+
</div>
148+
)}
116149
{onEdit && (
117150
<Button
118151
size="xsmall"

packages/compass-components/src/components/signal-popover.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ export type Signal = {
121121
type SignalPopoverProps = {
122122
/** List of signals to render */
123123
signals: Signal | Signal[];
124-
125124
darkMode?: boolean;
125+
onPopoverOpenChange?: (open: boolean) => void;
126126
};
127127

128128
const signalCardContentStyles = css({
@@ -438,6 +438,7 @@ const closeButtonMultiSignalStyles = css({
438438
const SignalPopover: React.FunctionComponent<SignalPopoverProps> = ({
439439
signals: _signals,
440440
darkMode: _darkMode,
441+
onPopoverOpenChange: _onPopoverOpenChange,
441442
}) => {
442443
const hooks = useContext(TrackingHooksContext);
443444
const darkMode = useDarkMode(_darkMode);
@@ -488,15 +489,19 @@ const SignalPopover: React.FunctionComponent<SignalPopoverProps> = ({
488489
return observer.disconnect.bind(observer);
489490
}, []);
490491

491-
const onPopoverOpenChange = useCallback((newStatus: boolean) => {
492-
setPopoverOpen(newStatus);
493-
// Reset current signal index when popover is being opened. If we do this on
494-
// close instead, the popover content is weirdly changed while the closing
495-
// animation is happening
496-
if (newStatus === true) {
497-
setCurrentSignalIndex(0);
498-
}
499-
}, []);
492+
const onPopoverOpenChange = useCallback(
493+
(newStatus: boolean) => {
494+
setPopoverOpen(newStatus);
495+
_onPopoverOpenChange?.(newStatus);
496+
// Reset current signal index when popover is being opened. If we do this on
497+
// close instead, the popover content is weirdly changed while the closing
498+
// animation is happening
499+
if (newStatus === true) {
500+
setCurrentSignalIndex(0);
501+
}
502+
},
503+
[_onPopoverOpenChange]
504+
);
500505

501506
const badgeLabel = multiSignals ? (
502507
<>{signals.length}&nbsp;insights</>

packages/compass-crud/src/components/editable-document.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Document } from 'hadron-document';
44
import HadronDocument from 'hadron-document';
55
import { DocumentList } from '@mongodb-js/compass-components';
66
import type { CrudActions } from '../stores/crud-store';
7+
import { withPreferences } from 'compass-preferences-model';
78

89
/**
910
* The base class.
@@ -33,12 +34,12 @@ const TEST_ID = 'editable-document';
3334
export type EditableDocumentProps = {
3435
doc: Document;
3536
expandAll?: boolean;
36-
3737
removeDocument?: CrudActions['removeDocument'];
3838
replaceDocument?: CrudActions['replaceDocument'];
3939
updateDocument?: CrudActions['updateDocument'];
4040
openInsertDocumentDialog?: CrudActions['openInsertDocumentDialog'];
4141
copyToClipboard?: CrudActions['copyToClipboard'];
42+
showInsights?: boolean;
4243
};
4344

4445
type EditableDocumentState = {
@@ -221,6 +222,18 @@ class EditableDocument extends React.Component<
221222
onClone={this.handleClone.bind(this)}
222223
onExpand={this.handleExpandAll.bind(this)}
223224
expanded={!!this.state.expandAll}
225+
insights={
226+
this.props.showInsights && (this.props.doc?.size ?? 0) > 10_000_000
227+
? {
228+
id: 'bloated-document',
229+
title: 'Possibly bloated document',
230+
description:
231+
'Large documents can slow down queries by decreasing the number of documents that can be stored in RAM. Consider breaking up your data into more collections with smaller documents, and using references to consolidate the data you need.',
232+
learnMoreLink:
233+
'https://www.mongodb.com/docs/atlas/schema-suggestions/reduce-document-size/',
234+
}
235+
: undefined
236+
}
224237
/>
225238
);
226239
}
@@ -299,7 +312,8 @@ class EditableDocument extends React.Component<
299312
updateDocument: PropTypes.func.isRequired,
300313
openInsertDocumentDialog: PropTypes.func.isRequired,
301314
copyToClipboard: PropTypes.func.isRequired,
315+
showInsights: PropTypes.bool,
302316
};
303317
}
304318

305-
export default EditableDocument;
319+
export default withPreferences(EditableDocument, ['showInsights'], React);

packages/compass-crud/src/components/readonly-document.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import { DocumentList } from '@mongodb-js/compass-components';
44
import type Document from 'hadron-document';
55
import type { TypeCastMap } from 'hadron-type-checker';
6+
import { withPreferences } from 'compass-preferences-model';
67
type BSONObject = TypeCastMap['Object'];
78

89
/**
@@ -25,6 +26,7 @@ export type ReadonlyDocumentProps = {
2526
openInsertDocumentDialog?: (doc: BSONObject, cloned: boolean) => void;
2627
doc: Document;
2728
expandAll: boolean;
29+
showInsights?: boolean;
2830
};
2931

3032
/**
@@ -66,6 +68,18 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
6668
onClone={
6769
this.props.openInsertDocumentDialog ? this.handleClone : undefined
6870
}
71+
insights={
72+
this.props.showInsights && (this.props.doc?.size ?? 0) > 10_000_000
73+
? {
74+
id: 'bloated-document',
75+
title: 'Possibly bloated document',
76+
description:
77+
'Large documents can slow down queries by decreasing the number of documents that can be stored in RAM. Consider breaking up your data into more collections with smaller documents, and using references to consolidate the data you need.',
78+
learnMoreLink:
79+
'https://www.mongodb.com/docs/atlas/schema-suggestions/reduce-document-size/',
80+
}
81+
: undefined
82+
}
6983
/>
7084
);
7185
}
@@ -93,7 +107,8 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
93107
doc: PropTypes.object.isRequired,
94108
expandAll: PropTypes.bool,
95109
openInsertDocumentDialog: PropTypes.func,
110+
showInsights: PropTypes.bool,
96111
};
97112
}
98113

99-
export default ReadonlyDocument;
114+
export default withPreferences(ReadonlyDocument, ['showInsights'], React);

packages/compass-crud/src/stores/crud-store.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Listenable, Store } from 'reflux';
22
import Reflux from 'reflux';
33
import toNS from 'mongodb-ns';
44
import { findIndex, isEmpty, isEqual } from 'lodash';
5+
import semver from 'semver';
56
// @ts-expect-error no types available
67
import StateMixin from 'reflux-state-mixin';
78
import type { Element } from 'hadron-document';
@@ -713,8 +714,6 @@ class CrudStoreImpl
713714
doc.emit('update-error', error.message);
714715
} else if (d) {
715716
doc.emit('update-success', d);
716-
this.localAppRegistry.emit('document-updated', this.state.view);
717-
this.globalAppRegistry.emit('document-updated', this.state.view);
718717
const index = this.findDocumentIndex(doc);
719718
this.state.docs![index] = new HadronDocument(d);
720719
this.trigger(this.state);
@@ -806,8 +805,6 @@ class CrudStoreImpl
806805
doc.emit('update-error', error.message);
807806
} else {
808807
doc.emit('update-success', d);
809-
this.localAppRegistry.emit('document-updated', this.state.view);
810-
this.globalAppRegistry.emit('document-updated', this.state.view);
811808
const index = this.findDocumentIndex(doc);
812809
this.state.docs![index] = new HadronDocument(d);
813810
this.trigger(this.state);
@@ -851,7 +848,7 @@ class CrudStoreImpl
851848
* @param {Number} page - The page that is being shown.
852849
*/
853850
async getPage(page: number) {
854-
const { ns, status, view } = this.state;
851+
const { ns, status } = this.state;
855852

856853
if (page < 0) {
857854
return;
@@ -909,7 +906,7 @@ class CrudStoreImpl
909906
const cancelDebounceLoad = this.debounceLoading();
910907

911908
let error: Error | undefined;
912-
let documents: BSONObject[];
909+
let documents: HadronDocument[];
913910
try {
914911
documents = await fetchDocuments(
915912
this.dataService,
@@ -933,7 +930,7 @@ class CrudStoreImpl
933930
status: error
934931
? DOCUMENTS_STATUS_ERROR
935932
: DOCUMENTS_STATUS_FETCHED_PAGINATION,
936-
docs: documents.map((doc: BSONObject) => new HadronDocument(doc)),
933+
docs: documents,
937934
// making sure we don't set start to 1 if length is 0
938935
start: length === 0 ? 0 : skip + 1,
939936
end: skip + length,
@@ -942,8 +939,10 @@ class CrudStoreImpl
942939
resultId: resultId(),
943940
abortController: null,
944941
});
945-
this.localAppRegistry.emit('documents-paginated', view, documents);
946-
this.globalAppRegistry.emit('documents-paginated', view, documents);
942+
this.localAppRegistry.emit(
943+
'documents-paginated',
944+
documents[0]?.generateObject()
945+
);
947946

948947
cancelDebounceLoad();
949948
}
@@ -964,7 +963,7 @@ class CrudStoreImpl
964963
* @param {Boolean} clone - Whether this is a clone operation.
965964
*/
966965
async openInsertDocumentDialog(doc: BSONObject, clone: boolean) {
967-
const hadronDoc = new HadronDocument(doc, false);
966+
const hadronDoc = new HadronDocument(doc);
968967

969968
if (clone) {
970969
track('Document Cloned', { mode: this.modeForTelemetry() });
@@ -1341,7 +1340,7 @@ class CrudStoreImpl
13411340
return;
13421341
}
13431342

1344-
const { ns, status, view, query } = this.state;
1343+
const { ns, status, query } = this.state;
13451344

13461345
if (status === DOCUMENTS_STATUS_FETCHING) {
13471346
return;
@@ -1489,16 +1488,18 @@ class CrudStoreImpl
14891488
? DOCUMENTS_STATUS_FETCHED_INITIAL
14901489
: DOCUMENTS_STATUS_FETCHED_CUSTOM,
14911490
error: null,
1492-
docs: docs.map((doc) => new HadronDocument(doc)),
1491+
docs: docs,
14931492
page: 0,
14941493
start: docs.length > 0 ? 1 : 0,
14951494
end: docs.length,
14961495
table: this.getInitialTableState(),
14971496
shardKeys,
14981497
});
14991498

1500-
this.localAppRegistry.emit('documents-refreshed', view, docs);
1501-
this.globalAppRegistry.emit('documents-refreshed', view, docs);
1499+
this.localAppRegistry.emit(
1500+
'documents-refreshed',
1501+
docs[0]?.generateObject()
1502+
);
15021503
} catch (error) {
15031504
log.error(
15041505
mongoLogId(1_001_000_074),

packages/compass-field-store/src/stores/store.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,17 +196,17 @@ const configureStore = (options = {}) => {
196196
if (options.localAppRegistry) {
197197
const appRegistry = options.localAppRegistry;
198198

199-
appRegistry.on('documents-refreshed', (view, docs) => {
200-
store.processSingleDocument(docs[0] || {});
199+
appRegistry.on('documents-refreshed', (doc = {}) => {
200+
store.processSingleDocument(doc);
201201
});
202202

203203
// process new document a user inserts
204204
appRegistry.on('document-inserted', ({ docs }) => {
205205
store.processSingleDocument(docs[0] || {});
206206
});
207207

208-
appRegistry.on('documents-paginated', (view, docs) => {
209-
store.processSingleDocument(docs[0] || {});
208+
appRegistry.on('documents-paginated', (doc = {}) => {
209+
store.processSingleDocument(doc);
210210
});
211211

212212
// optionally also subscribe to the SchemaStore if present

packages/compass-field-store/src/stores/store.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe('FieldStore', function () {
101101
]);
102102
expect(state.autocompleteFields).to.deep.equal(expected);
103103
}, done);
104-
appRegistry.emit('documents-refreshed', null, [doc]);
104+
appRegistry.emit('documents-refreshed', doc);
105105
});
106106

107107
it('on documents-inserted', function (done) {
@@ -125,7 +125,7 @@ describe('FieldStore', function () {
125125
]);
126126
expect(state.autocompleteFields).to.deep.equal(expected);
127127
}, done);
128-
appRegistry.emit('documents-paginated', null, [doc]);
128+
appRegistry.emit('documents-paginated', doc);
129129
});
130130
});
131131
/**

0 commit comments

Comments
 (0)