Skip to content

Commit a31253f

Browse files
gribnoysupaddaleax
andauthored
chore(compass-collection): separate collection tab content from collection tabs management logic (#4858)
* chore(compass-collection): separate collection tab content from collection tabs management logic * chore(compass-collection): subscribe to the collection model directly; pick stats props from modal before storing in state * chore(compass-collection): simplify feature flag check Co-authored-by: Anna Henningsen <anna.henningsen@mongodb.com> * chore(compass-collection): add tests for tabs store * chore(compass-collection): add tests for collection tab store * chore(compass-collection): re-enable workspace component tests * chore(saved-queries, app-stores): do not emit open-namespace directly from my-queries plugin; add descriptions to collection metadata * chore(compass-collection): use instance.env instead of atlas (prepare for #4875 to be merged) --------- Co-authored-by: Anna Henningsen <anna.henningsen@mongodb.com>
1 parent 51bf6ce commit a31253f

File tree

73 files changed

+2195
-3876
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2195
-3876
lines changed

package-lock.json

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/collection-model/index.d.ts

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,51 @@
11
import type toNS from 'mongodb-ns';
22
import type { DataService } from 'mongodb-data-service';
33

4-
type Namespace = ReturnType<typeof toNS>;
4+
type CollectionMetadata = {
5+
/**
6+
* Collection namespace (<database>.<collection>)
7+
*/
8+
namespace: string;
9+
/**
10+
* Indicates that colleciton is read only
11+
*/
12+
isReadonly: boolean;
13+
/**
14+
* Indicates that colleciton is a time series collection
15+
*/
16+
isTimeSeries: boolean;
17+
/**
18+
* Indicates that collection is clustered / has a clustered index
19+
*/
20+
isClustered: boolean;
21+
/**
22+
* Indicates that collection has encrypted fields in it
23+
*/
24+
isFLE: boolean;
25+
/**
26+
* Indicates that MongoDB server supports search indexes (property is exposed
27+
* on collection because the check is relevant for collection tab and requires
28+
* collection namespace to perform the check)
29+
*/
30+
isSearchIndexesSupported: boolean;
31+
/**
32+
* View source namespace (<database>.<collection>)
33+
*/
34+
sourceName?: string;
35+
/**
36+
* Indicates if a source collection is read only
37+
*/
38+
sourceReadonly?: boolean;
39+
/**
40+
* View source view namespace (this is the same as metadata namespace if
41+
* present)
42+
*/
43+
sourceViewon?: string;
44+
/**
45+
* Aggregation pipeline view definition
46+
*/
47+
sourcePipeline?: unknown[];
48+
};
549

650
interface Collection {
751
_id: string;
@@ -40,18 +84,10 @@ interface Collection {
4084
fetchInfo?: boolean;
4185
force?: boolean;
4286
}): Promise<void>;
43-
fetchMetadata(opts: { dataService: DataService }): Promise<{
44-
namespace: string;
45-
isReadonly: boolean;
46-
isTimeSeries: boolean;
47-
isClustered: boolean;
48-
isFLE: boolean;
49-
isSearchIndexesSupported: boolean;
50-
sourceName?: string;
51-
sourceReadonly?: boolean;
52-
sourceViewon?: string;
53-
sourcePipeline?: unknown[];
54-
}>;
87+
fetchMetadata(opts: {
88+
dataService: DataService;
89+
}): Promise<CollectionMetadata>;
90+
on(evt: string, fn: (...args: any) => void);
5591
toJSON(opts?: { derived: boolean }): this;
5692
}
5793

@@ -63,4 +99,4 @@ interface CollectionCollection extends Array<Collection> {
6399
}
64100

65101
export default Collection;
66-
export { CollectionCollection };
102+
export { CollectionCollection, CollectionMetadata };

packages/compass-aggregations/src/plugin.jsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ import Aggregations from './components/aggregations';
44
import { Provider } from 'react-redux';
55
import configureStore from './stores/store';
66
import { ConfirmationModalArea } from '@mongodb-js/compass-components';
7+
import { withPreferences } from 'compass-preferences-model';
78

8-
class Plugin extends Component {
9-
static displayName = 'AggregationsPlugin';
10-
9+
class AggregationsPlugin extends Component {
1110
static propTypes = {
1211
store: PropTypes.object.isRequired,
13-
showExportButton: PropTypes.bool,
14-
showRunButton: PropTypes.bool,
15-
showExplainButton: PropTypes.bool,
12+
enableImportExport: PropTypes.bool,
13+
enableAggregationBuilderRunPipeline: PropTypes.bool,
14+
enableExplainPlan: PropTypes.bool,
1615
};
1716

1817
static defaultProps = {
19-
showExportButton: false,
20-
showRunButton: false,
21-
showExplainButton: false,
18+
enableImportExport: false,
19+
enableAggregationBuilderRunPipeline: false,
20+
enableExplainPlan: false,
2221
};
2322

2423
/**
@@ -29,15 +28,25 @@ class Plugin extends Component {
2928
<ConfirmationModalArea>
3029
<Provider store={this.props.store}>
3130
<Aggregations
32-
showExportButton={this.props.showExportButton}
33-
showRunButton={this.props.showRunButton}
34-
showExplainButton={this.props.showExplainButton}
31+
showExportButton={this.props.enableImportExport}
32+
showRunButton={this.props.enableAggregationBuilderRunPipeline}
33+
showExplainButton={this.props.enableExplainPlan}
3534
/>
3635
</Provider>
3736
</ConfirmationModalArea>
3837
);
3938
}
4039
}
4140

41+
const Plugin = withPreferences(
42+
AggregationsPlugin,
43+
[
44+
'enableImportExport',
45+
'enableAggregationBuilderRunPipeline',
46+
'enableExplainPlan',
47+
],
48+
React
49+
);
50+
4251
export default Plugin;
4352
export { Plugin, configureStore };

packages/compass-aggregations/src/plugin.spec.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import Aggregations from './plugin';
66

77
const renderPlugin = () => {
88
const store = configureStore();
9-
render(
10-
<Aggregations showExportButton={true} showRunButton={true} store={store} />
11-
);
9+
render(<Aggregations store={store} />);
1210
};
1311

1412
describe('Aggregations [Plugin]', function () {

packages/compass-app-stores/src/stores/instance-store.js

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ function getTopologyDescription(topologyDescription) {
2323

2424
const store = createStore(reducer);
2525

26+
store.getInstance = () => {
27+
return store.getState().instance;
28+
};
29+
2630
store.refreshInstance = async (globalAppRegistry, refreshOptions) => {
2731
const { instance, dataService } = store.getState();
2832

@@ -247,6 +251,20 @@ store.onActivated = (appRegistry) => {
247251
store.refreshInstance(appRegistry);
248252
});
249253

254+
appRegistry.on('database-dropped', async () => {
255+
const { instance, dataService } = store.getState();
256+
await instance.fetchDatabases({ dataService, force: true });
257+
});
258+
259+
appRegistry.on('collection-dropped', async (namespace) => {
260+
const { instance, dataService } = store.getState();
261+
const { database } = toNS(namespace);
262+
await instance.fetchDatabases({ dataService, force: true });
263+
const db = instance.databases.get(database);
264+
// If it was last collection, there will be no db returned
265+
await db?.fetchCollections({ dataService, force: true });
266+
});
267+
250268
// Event emitted when the Databases grid needs to be refreshed
251269
// We additionally refresh the list of collections as well
252270
// since there is the side navigation which could be in expanded mode
@@ -268,7 +286,6 @@ store.onActivated = (appRegistry) => {
268286
if (!instance.databases.get(database)) {
269287
await instance.fetchDatabases({ dataService, force: true });
270288
}
271-
272289
const db = instance.databases.get(database);
273290
if (db) {
274291
await db.fetchCollectionsDetails({ dataService, force: true });
@@ -297,46 +314,75 @@ store.onActivated = (appRegistry) => {
297314
appRegistry.emit('select-namespace', metadata);
298315
});
299316

300-
appRegistry.on('active-collection-dropped', async (ns) => {
301-
const { instance, dataService } = store.getState();
302-
const { database } = toNS(ns);
303-
await store.fetchDatabaseDetails(database);
304-
const db = instance.databases.get(database);
305-
await db.fetchCollections({ dataService, force: true });
306-
307-
if (db.collectionsLength) {
308-
appRegistry.emit('select-database', database);
309-
} else {
310-
appRegistry.emit('open-instance-workspace', 'Databases');
311-
}
317+
appRegistry.on('active-collection-dropped', (ns) => {
318+
// This callback will fire after drop collection happened, we force it into
319+
// a microtask to allow drop collections event handler to force start
320+
// databases and collections list update before we run our check here
321+
queueMicrotask(async () => {
322+
const { instance, dataService } = store.getState();
323+
const { database } = toNS(ns);
324+
await instance.fetchDatabases({ dataService });
325+
const db = instance.databases.get(database);
326+
await db?.fetchCollections({ dataService });
327+
if (db?.collectionsLength) {
328+
appRegistry.emit('select-database', database);
329+
} else {
330+
appRegistry.emit('open-instance-workspace', 'Databases');
331+
}
332+
});
312333
});
313334

314335
appRegistry.on('active-database-dropped', async () => {
315336
appRegistry.emit('open-instance-workspace', 'Databases');
316337
});
317338

318-
appRegistry.on('collections-list-select-collection', async ({ ns }) => {
339+
/**
340+
* Opens collection in the current active tab. No-op if currently open tab has
341+
* the same namespace. Additional `query` and `agrregation` props can be
342+
* passed with the namespace to open tab with initial query or aggregation
343+
* pipeline
344+
*/
345+
const openCollectionInSameTab = async ({ ns, ...extraMetadata }) => {
319346
const metadata = await store.fetchCollectionMetadata(ns);
320-
appRegistry.emit('select-namespace', metadata);
321-
});
322-
323-
appRegistry.on('sidebar-select-collection', async ({ ns }) => {
324-
const metadata = await store.fetchCollectionMetadata(ns);
325-
appRegistry.emit('select-namespace', metadata);
326-
});
347+
appRegistry.emit('select-namespace', {
348+
...metadata,
349+
...extraMetadata,
350+
});
351+
};
327352

328-
const openCollectionInNewTab = async ({ ns }) => {
353+
appRegistry.on('collections-list-select-collection', openCollectionInSameTab);
354+
appRegistry.on('sidebar-select-collection', openCollectionInSameTab);
355+
appRegistry.on(
356+
'collection-workspace-select-namespace',
357+
openCollectionInSameTab
358+
);
359+
appRegistry.on('collection-tab-select-collection', openCollectionInSameTab);
360+
361+
/**
362+
* Opens collection in a new tab. Additional `query` and `agrregation` props
363+
* can be passed with the namespace to open tab with initial query or
364+
* aggregation pipeline
365+
*/
366+
const openCollectionInNewTab = async ({ ns, ...extraMetadata }) => {
329367
const metadata = await store.fetchCollectionMetadata(ns);
330-
appRegistry.emit('open-namespace-in-new-tab', metadata);
368+
appRegistry.emit('open-namespace-in-new-tab', {
369+
...metadata,
370+
...extraMetadata,
371+
});
331372
};
332373

333374
appRegistry.on('sidebar-open-collection-in-new-tab', openCollectionInNewTab);
334375
appRegistry.on(
335376
'import-export-open-collection-in-new-tab',
336377
openCollectionInNewTab
337378
);
379+
appRegistry.on(
380+
'collection-workspace-open-collection-in-new-tab',
381+
openCollectionInNewTab
382+
);
383+
appRegistry.on('my-queries-open-saved-item', openCollectionInNewTab);
338384

339-
appRegistry.on('sidebar-modify-view', async ({ ns }) => {
385+
const openModifyView = async ({ ns, sameTab }) => {
340386
const coll = await store.fetchCollectionDetails(ns);
341387
if (coll.sourceId && coll.pipeline) {
342388
// `modify-view` is currently implemented in a way where we are basically
@@ -349,13 +395,21 @@ store.onActivated = (appRegistry) => {
349395
const metadata = await store.fetchCollectionMetadata(coll.sourceId);
350396
metadata.sourcePipeline = coll.pipeline;
351397
metadata.editViewName = coll.ns;
352-
appRegistry.emit('open-namespace-in-new-tab', metadata);
398+
appRegistry.emit(
399+
sameTab ? 'select-namespace' : 'open-namespace-in-new-tab',
400+
metadata
401+
);
353402
} else {
354403
debug(
355404
'Tried to modify the view on a collection with required metadata missing',
356405
coll.toJSON()
357406
);
358407
}
408+
};
409+
410+
appRegistry.on('sidebar-modify-view', openModifyView);
411+
appRegistry.on('collection-tab-modify-view', ({ ns }) => {
412+
openModifyView({ ns, sameTab: true });
359413
});
360414

361415
appRegistry.on('sidebar-duplicate-view', async ({ ns }) => {
@@ -379,7 +433,6 @@ store.onActivated = (appRegistry) => {
379433
async function (namespace) {
380434
// Force-refresh specified namespace to update collections list and get
381435
// collection info / stats (in case of opening result collection we're
382-
383436
// always assuming the namespace wasn't yet updated)
384437
await store.refreshNamespace(toNS(namespace));
385438
// Now we can get the metadata from already fetched collection and open a

packages/compass-collection/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@
9090
"chai": "^4.3.6",
9191
"depcheck": "^1.4.1",
9292
"eslint": "^7.25.0",
93-
"lodash": "^4.17.21",
9493
"mocha": "^10.2.0",
9594
"mongodb": "^6.0.0",
9695
"mongodb-collection-model": "^5.11.0",
9796
"mongodb-data-service": "^22.11.0",
97+
"mongodb-instance-model": "^12.11.0",
9898
"mongodb-ns": "^2.4.0",
9999
"numeral": "^2.0.6",
100100
"nyc": "^15.1.0",

0 commit comments

Comments
 (0)