Skip to content

Commit

Permalink
chore(workspaces, collection): resolve collection metadata in collect…
Browse files Browse the repository at this point in the history
…ion tab plugin COMPASS-7354 (#5204)

* chore(workspaces, collection): resolve collection metadata in collection tab plugin

* chore(workspaces): pass collection info to the workspace change callback

* chore(sidebar): hide navigation items in the sidebar while we are fetching instance info

* chore(sidebar): update test
  • Loading branch information
gribnoysup authored Dec 8, 2023
1 parent c5c8984 commit 86bb8c0
Show file tree
Hide file tree
Showing 32 changed files with 714 additions and 541 deletions.
5 changes: 4 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 20 additions & 12 deletions packages/collection-model/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,24 @@ type CollectionMetadata = {
*/
sourceName?: string;
/**
* Indicates if a source collection is read only
* View source pipeline definition
*/
sourceReadonly?: boolean;
sourcePipeline?: unknown[];
/**
* View source view namespace (this is the same as metadata namespace if
* present)
* Instance metadata: whether or not we are connected to the ADF
*/
sourceViewon?: string;
isDataLake: boolean;
/**
* Aggregation pipeline view definition
* Instance metadata: whether or not we are connected to Atlas Cloud
*/
sourcePipeline?: unknown[];
isAtlas: boolean;
/**
* Instance metadata: current connection server version
*/
serverVersion: string;
};

interface Collection {
interface CollectionProps {
_id: string;
type: string;
status: 'initial' | 'fetching' | 'refreshing' | 'ready' | 'error';
Expand Down Expand Up @@ -76,9 +79,12 @@ interface Collection {
index_size: number;
isTimeSeries: boolean;
isView: boolean;
sourceId: string | null;
sourceName: string | null;
source: Collection;
properties: { id: string; options?: unknown }[];
}

interface Collection extends CollectionProps {
fetch(opts: {
dataService: DataService;
fetchInfo?: boolean;
Expand All @@ -90,15 +96,17 @@ interface Collection {
on(evt: string, fn: (...args: any) => void);
off(evt: string, fn: (...args: any) => void);
removeListener(evt: string, fn: (...args: any) => void);
toJSON(opts?: { derived: boolean }): this;
toJSON(opts?: { derived: boolean }): CollectionProps;
previousAttributes(): CollectionProps;
set(val: Partial<CollectionProps>): this;
}

interface CollectionCollection extends Array<Collection> {
fetch(opts: { dataService: DataService; fetchInfo?: boolean }): Promise<void>;
toJSON(opts?: { derived: boolean }): this;
toJSON(opts?: { derived: boolean }): Array<CollectionProps>;
at(index: number): Collection | undefined;
get(id: string, key?: '_id' | 'name'): Collection | undefined;
}

export default Collection;
export { CollectionCollection, CollectionMetadata };
export { CollectionCollection, CollectionMetadata, CollectionProps };
34 changes: 16 additions & 18 deletions packages/collection-model/lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,16 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), {
return this.type === 'view';
},
},
sourceId: {
sourceName: {
deps: ['view_on'],
fn() {
return this.view_on ? `${this.database}.${this.view_on}` : null;
},
},
source: {
deps: ['sourceId'],
deps: ['sourceName'],
fn() {
return this.collection.get(this.sourceId) ?? null;
return this.collection.get(this.sourceName) ?? null;
},
},
properties: {
Expand Down Expand Up @@ -269,18 +269,21 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), {
// We don't care if it fails to get stats from server for any reason
}

const instance = getParentByType(this, 'Instance');

/**
* The support for search indexes is a feature of a server not a collection.
* As this check can only be performed currently by running $listSearchIndexes
* aggregation stage against a collection, so we run it from the collection model.
* With this setup, when a user opens the first collection, we set this property
* on the instance model and then from there its value is read avoiding call to server.
*/
const isSearchIndexesSupported = await getParentByType(this, 'Instance')
.getIsSearchSupported({
const isSearchIndexesSupported =
!this.isView &&
(await instance.getIsSearchSupported({
dataService,
ns: this.ns,
});
}));

const collectionMetadata = {
namespace: this.ns,
Expand All @@ -289,23 +292,18 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), {
isClustered: this.clustered,
isFLE: this.fle2,
isSearchIndexesSupported,
isDataLake: instance.dataLake.isDataLake,
isAtlas: instance.env === 'atlas',
serverVersion: instance.build.version,
};
if (this.sourceId) {
try {
await this.source.fetch({ dataService });
} catch (e) {
if (e.name !== 'MongoServerError') {
throw e;
}
// We don't care if it fails to get stats from server for any reason
}

if (this.sourceName) {
Object.assign(collectionMetadata, {
sourceName: this.source.ns,
sourceReadonly: this.source.readonly,
sourceViewon: this.source.sourceId,
sourceName: this.sourceName,
sourcePipeline: this.pipeline,
});
}

return collectionMetadata;
},

Expand Down
30 changes: 10 additions & 20 deletions packages/compass-aggregations/src/stores/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,21 @@ export type ConfigureStoreOptions = {
* Stored pipeline metadata. Can be provided to preload stored pipeline
* right when the plugin is initialized
*/
aggregation: unknown;
aggregation?: unknown;
/**
* Namespace for the view that is being edited. Needs to be provided
* with the `sourcePipeline` options. Takes precedence over `pipeline`
* option
* Namespace for the view that is being edited. Needs to be provided with the
* `pipeline` options
*/
editViewName: string;
/**
* Pipeline definition for the view that is being edited. Needs to be
* provided with the `editViewName` option. Takes precedence over
* `pipeline` option
*/
sourcePipeline: unknown[];
/**
* Initial pipeline that will be converted to a string to be used by the
* aggregation builder. Takes precedence over `pipelineText` option
*/
pipeline: unknown[];
pipeline?: unknown[];
/**
* Initial pipeline text to be used by the aggregation builder
*/
pipelineText: string;
pipelineText?: string;
/**
* List of all the collections in the current database. It is used inside
* the stage wizard to populate the dropdown for $lookup use-case.
Expand Down Expand Up @@ -148,20 +141,17 @@ export function activateAggregationsPlugin(
cleanup.push(() => eventEmitter.removeListener(ev, listener));
}

if (options.editViewName && !options.sourcePipeline) {
if (options.editViewName && !options.pipeline) {
throw new Error(
'Option `editViewName` can be used only if `sourcePipeline` is provided'
'Option `editViewName` can be used only if `pipeline` is provided'
);
}

const editingView = !!(options.editViewName && options.sourcePipeline);
const editingView = !!(options.editViewName && options.pipeline);

const initialPipelineSource =
(editingView
? toJSString(options.sourcePipeline, ' ')
: options.pipeline
? toJSString(options.pipeline)
: options.pipelineText) ?? undefined;
(options.pipeline ? toJSString(options.pipeline) : options.pipelineText) ??
undefined;

const { collection } = toNS(options.namespace);

Expand Down
62 changes: 9 additions & 53 deletions packages/compass-app-stores/src/stores/instance-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,6 @@ export function createInstanceStore({
onSidebarFilterNavigationList
);

const onSelectNamespace = ({ namespace }: { namespace: string }) =>
void fetchCollectionDetails(namespace);
onAppRegistryEvent('select-namespace', onSelectNamespace);

const onOpenNamespaceInNewTab = ({ namespace }: { namespace: string }) =>
void fetchCollectionDetails(namespace);
onAppRegistryEvent('open-namespace-in-new-tab', onOpenNamespaceInNewTab);

const onRefreshData = voidify(refreshInstance);
onAppRegistryEvent('refresh-data', onRefreshData);

Expand Down Expand Up @@ -287,15 +279,13 @@ export function createInstanceStore({
});
onAppRegistryEvent('refresh-databases', onRefreshDatabases);

const onCollectionRenamed = voidify(
async ({ from, to }: { from: string; to: string }) => {
const { database } = toNS(from);
await refreshNamespace({
ns: to,
database,
});
}
);
const onCollectionRenamed = ({ from, to }: { from: string; to: string }) => {
const { database, collection } = toNS(from);
instance.databases
.get(database)
?.collections.get(collection, 'name')
?.set({ _id: to });
};
appRegistry.on('collection-renamed', onCollectionRenamed);

const onAggPipelineOutExecuted = voidify(refreshInstance);
Expand Down Expand Up @@ -398,45 +388,11 @@ export function createInstanceStore({
onOpenResultNamespace
);

const openModifyView = voidify(
async ({ ns, sameTab }: { ns: string; sameTab: boolean }) => {
const coll = await fetchCollectionDetails(ns);
if (coll?.sourceId && coll?.pipeline) {
// `modify-view` is currently implemented in a way where we are basically
// just opening a new tab but for a source collection instead of a view
// and with source pipeline of this new tab set to the view pipeline
// instead of the actual source pipeline of the view source. This
// definitely feels like putting too much logic on the same property, but
// refactoring this away would require us to change way too many things in
// the collection / aggregation plugins, so we're just keeping it as it is
const metadata: Record<string, unknown> =
(await fetchCollectionMetadata(coll.sourceId)) ?? {};
metadata.sourcePipeline = coll.pipeline;
metadata.editViewName = coll.ns;
appRegistry.emit(
sameTab ? 'select-namespace' : 'open-namespace-in-new-tab',
metadata
);
} else {
debug(
'Tried to modify the view on a collection with required metadata missing',
coll?.toJSON()
);
}
}
);

onAppRegistryEvent('sidebar-modify-view', openModifyView);
const onCollectionTabModifyView = ({ ns }: { ns: string }) => {
openModifyView({ ns, sameTab: true });
};
onAppRegistryEvent('collection-tab-modify-view', onCollectionTabModifyView);

const onSidebarDuplicateView = voidify(async ({ ns }) => {
const coll = await fetchCollectionDetails(ns);
if (coll?.sourceId && coll?.pipeline) {
if (coll?.sourceName && coll?.pipeline) {
appRegistry.emit('open-create-view', {
source: coll.sourceId,
source: coll.sourceName,
pipeline: coll.pipeline,
duplicate: true,
});
Expand Down
Loading

0 comments on commit 86bb8c0

Please sign in to comment.