Skip to content

Commit

Permalink
feat(databases-collections, compass-sidebar): add rename collection f…
Browse files Browse the repository at this point in the history
…low COMPASS-5704 (mongodb-js#5063)
  • Loading branch information
baileympearson authored Nov 29, 2023
1 parent f54bb15 commit d3d1c1d
Show file tree
Hide file tree
Showing 28 changed files with 1,278 additions and 58 deletions.
2 changes: 2 additions & 0 deletions package-lock.json

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

18 changes: 18 additions & 0 deletions packages/compass-app-stores/src/stores/instance-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,24 @@ export function createInstanceStore({
});
onAppRegistryEvent('refresh-databases', onRefreshDatabases);

const onCollectionRenamed = voidify(
async ({ from, to }: { from: string; to: string }) => {
// we must fetch the old collection's metadata before refreshing because refreshing the
// collection metadata will remove the old collection from the model.
const metadata = await fetchCollectionMetadata(from);
appRegistry.emit('refresh-collection-tabs', {
metadata,
newNamespace: to,
});
const { database } = toNS(from);
await refreshNamespace({
ns: to,
database,
});
}
);
appRegistry.on('collection-renamed', onCollectionRenamed);

// Event emitted when the Collections grid needs to be refreshed
// with new collections or collection stats for existing ones.
const onRefreshCollections = voidify(async ({ ns }: { ns: string }) => {
Expand Down
38 changes: 38 additions & 0 deletions packages/compass-collection/src/modules/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum CollectionTabsActions {
DatabaseDropped = 'compass-collection/DatabaseDropped',
DataServiceConnected = 'compass-collection/DataServiceConnected',
DataServiceDisconnected = 'compass-collection/DataServiceDisconnected',
CollectionRenamed = 'compass-collection/CollectionRenamed',
}

type CollectionTabsThunkAction<
Expand Down Expand Up @@ -182,6 +183,17 @@ const reducer: Reducer<CollectionTabsState> = (
tabs: newTabs,
};
}
if (action.type === CollectionTabsActions.CollectionRenamed) {
const { tabs } = action;

const activeTabIndex = getActiveTabIndex(state);
const activeTabId = tabs[activeTabIndex]?.id ?? null;
return {
...state,
tabs,
activeTabId,
};
}
return state;
};

Expand Down Expand Up @@ -391,4 +403,30 @@ export const dataServiceDisconnected = () => {
return { type: CollectionTabsActions.DataServiceDisconnected };
};

export const collectionRenamed = ({
from,
newNamespace,
}: {
from: CollectionMetadata;
newNamespace: string;
}): CollectionTabsThunkAction<void> => {
return (dispatch, getState) => {
const tabs = getState().tabs.map((tab) =>
tab.namespace === from.namespace
? dispatch(
createNewTab({
...from,
namespace: newNamespace,
})
)
: tab
);

dispatch({
type: CollectionTabsActions.CollectionRenamed,
tabs,
});
};
};

export default reducer;
1 change: 1 addition & 0 deletions packages/compass-collection/src/stores/tabs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ describe('Collection Tabs Store', function () {
'select-namespace',
'collection-dropped',
'database-dropped',
'refresh-collection-tabs',
'data-service-connected',
'data-service-disconnected',
'menu-share-schema-json',
Expand Down
20 changes: 20 additions & 0 deletions packages/compass-collection/src/stores/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import tabs, {
getActiveTab,
dataServiceDisconnected,
dataServiceConnected,
collectionRenamed,
} from '../modules/tabs';
import { globalAppRegistry } from 'hadron-app-registry';
import type { CollectionMetadata } from 'mongodb-collection-model';

type ThunkExtraArg = {
globalAppRegistry: AppRegistry;
Expand Down Expand Up @@ -81,6 +83,24 @@ export function configureStore({
store.dispatch(databaseDropped(namespace));
});

globalAppRegistry.on(
'refresh-collection-tabs',
({
metadata,
newNamespace,
}: {
metadata: CollectionMetadata;
newNamespace: string;
}) => {
store.dispatch(
collectionRenamed({
from: metadata,
newNamespace,
})
);
}
);

/**
* Set the data service in the store when connected.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-components/src/hooks/use-toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const defaultToastProperties: Partial<ToastProperties> = {
dismissible: true,
};

interface ToastActions {
export interface ToastActions {
openToast: (id: string, toastProperties: ToastProperties) => void;
closeToast: (id: string) => void;
}
Expand Down
1 change: 1 addition & 0 deletions packages/compass-databases-navigation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
},
"dependencies": {
"@mongodb-js/compass-components": "^1.19.0",
"compass-preferences-model": "^2.15.6",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6"
},
Expand Down
25 changes: 20 additions & 5 deletions packages/compass-databases-navigation/src/collection-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
NamespaceItemProps,
} from './tree-item';
import type { Actions } from './constants';
import { usePreference } from 'compass-preferences-model';

const CollectionIcon: React.FunctionComponent<{
type: string;
Expand Down Expand Up @@ -64,6 +65,10 @@ export const CollectionItem: React.FunctionComponent<
style,
onNamespaceAction,
}) => {
const isRenameCollectionEnabled = usePreference(
'enableRenameCollectionModal',
React
);
const [hoverProps, isHovered] = useHoverState();

const onDefaultAction = useCallback(
Expand Down Expand Up @@ -121,16 +126,26 @@ export const CollectionItem: React.FunctionComponent<
icon: 'Edit',
}
);
} else {

return actions;
}

if (type !== 'timeseries' && isRenameCollectionEnabled) {
actions.push({
action: 'drop-collection',
label: 'Drop collection',
icon: 'Trash',
action: 'rename-collection',
label: 'Rename collection',
icon: 'Edit',
});
}

actions.push({
action: 'drop-collection',
label: 'Drop collection',
icon: 'Trash',
});

return actions;
}, [type, isReadOnly]);
}, [type, isReadOnly, isRenameCollectionEnabled]);

return (
<ItemContainer
Expand Down
3 changes: 2 additions & 1 deletion packages/compass-databases-navigation/src/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export type Actions =
| 'drop-collection'
| 'open-in-new-tab'
| 'duplicate-view'
| 'modify-view';
| 'modify-view'
| 'rename-collection';
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import userEvent from '@testing-library/user-event';
import { expect } from 'chai';
import Sinon from 'sinon';
import { DatabasesNavigationTree } from './databases-navigation-tree';
import preferencesAccess from 'compass-preferences-model';

const databases = [
{
Expand Down Expand Up @@ -42,6 +43,60 @@ const TEST_VIRTUAL_PROPS = {
describe('DatabasesNavigationTree', function () {
afterEach(cleanup);

context('when the rename collection feature flag is enabled', () => {
const sandbox = Sinon.createSandbox();
before(() => {
sandbox.stub(preferencesAccess, 'getPreferences').returns({
enableRenameCollectionModal: true,
} as any);
});

after(() => sandbox.restore());

it('shows the Rename Collection action', function () {
render(
<DatabasesNavigationTree
databases={databases}
expanded={{ bar: true }}
activeNamespace="bar.meow"
onDatabaseExpand={() => {}}
onNamespaceAction={() => {}}
{...TEST_VIRTUAL_PROPS}
></DatabasesNavigationTree>
);

const collection = screen.getByTestId('sidebar-collection-bar.meow');
const showActionsButton = within(collection).getByTitle('Show actions');

expect(within(collection).getByTitle('Show actions')).to.exist;

userEvent.click(showActionsButton);

expect(screen.getByText('Rename collection')).to.exist;
});

it('should activate callback with `rename-collection` when corresponding action is clicked', function () {
const spy = Sinon.spy();
render(
<DatabasesNavigationTree
databases={databases}
expanded={{ bar: true }}
activeNamespace="bar.meow"
onNamespaceAction={spy}
onDatabaseExpand={() => {}}
{...TEST_VIRTUAL_PROPS}
></DatabasesNavigationTree>
);

const collection = screen.getByTestId('sidebar-collection-bar.meow');

userEvent.click(within(collection).getByTitle('Show actions'));
userEvent.click(screen.getByText('Rename collection'));

expect(spy).to.be.calledOnceWithExactly('bar.meow', 'rename-collection');
});
});

it('should render databases', function () {
render(
<DatabasesNavigationTree
Expand Down Expand Up @@ -166,6 +221,7 @@ describe('DatabasesNavigationTree', function () {
userEvent.click(showActionsButton);

expect(screen.getByText('Open in new tab')).to.exist;
expect(() => screen.getByText('Rename collection')).to.throw;
expect(screen.getByText('Drop collection')).to.exist;
});

Expand All @@ -192,6 +248,9 @@ describe('DatabasesNavigationTree', function () {
expect(screen.getByText('Drop view')).to.exist;
expect(screen.getByText('Duplicate view')).to.exist;
expect(screen.getByText('Modify view')).to.exist;

// views cannot be renamed
expect(() => screen.getByText('Rename collection')).to.throw;
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Selectors } from '../compass';
import type { CompassBrowser } from '../compass-browser';

/**
* Saves an aggregation pipeline.
*
* This helper expects that the browser is already on the aggregations tab for the target collection.
*/
export async function saveAggregationPipeline(
browser: CompassBrowser,
aggregationName: string,
pipeline: Record<string, any>[]
) {
for (let index = 0; index < pipeline.length; index++) {
const stage = pipeline[index];
const stageOperator = Object.keys(stage)[0];
const stageValue = stage[stageOperator];

// add stage
await browser.clickVisible(Selectors.AddStageButton);
await browser.$(Selectors.stageEditor(index)).waitForDisplayed();

await browser.focusStageOperator(index);
await browser.selectStageOperator(index, stageOperator);
await browser.setCodemirrorEditorValue(
Selectors.stageEditor(index),
stageValue
);
}

await browser.clickVisible(Selectors.SavePipelineMenuButton);
const menuElement = await browser.$(Selectors.SavePipelineMenuContent);
await menuElement.waitForDisplayed();
await browser.clickVisible(Selectors.SavePipelineSaveAsAction);

// wait for the modal to appear
const savePipelineModal = await browser.$(Selectors.SavePipelineModal);
await savePipelineModal.waitForDisplayed();

// set aggregation name
await browser.waitForAnimations(Selectors.SavePipelineNameInput);
const pipelineNameInput = await browser.$(Selectors.SavePipelineNameInput);
await pipelineNameInput.setValue(aggregationName);

const createButton = await browser
.$(Selectors.SavePipelineModal)
.$('button=Save');

await createButton.click();
}
12 changes: 7 additions & 5 deletions packages/compass-e2e-tests/helpers/insert-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import type { Db, MongoServerError } from 'mongodb';

const CONNECTION_URI = 'mongodb://localhost:27091';

async function dropDatabase(db: Db) {
export async function dropDatabase(db: Db | string) {
const database = typeof db === 'string' ? client.db(db) : db;
try {
await db.dropDatabase();
await database.dropDatabase();
} catch (err) {
const codeName = (err as MongoServerError).codeName;
if (codeName !== 'NamespaceNotFound') {
Expand All @@ -14,13 +15,14 @@ async function dropDatabase(db: Db) {
}
}

async function createBlankCollection(db: Db, name: string) {
export async function createBlankCollection(db: Db | string, name: string) {
const database = typeof db === 'string' ? client.db(db) : db;
try {
await db.createCollection(name);
await database.createCollection(name);
} catch (err) {
const codeName = (err as MongoServerError).codeName;
if (codeName === 'NamespaceExists') {
await db.collection(name).deleteMany({});
await database.collection(name).deleteMany({});
} else {
throw err;
}
Expand Down
Loading

0 comments on commit d3d1c1d

Please sign in to comment.