diff --git a/package-lock.json b/package-lock.json index fb22d0ca05d..80b372e6fe3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46047,6 +46047,7 @@ "@mongodb-js/compass-import-export": "^7.19.1", "@mongodb-js/compass-instance": "^4.19.1", "@mongodb-js/compass-logging": "^1.2.6", + "@mongodb-js/compass-saved-aggregations-queries": "^1.20.1", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", "@mongodb-js/compass-sidebar": "^5.19.1", @@ -46093,6 +46094,7 @@ "@mongodb-js/compass-import-export": "^7.19.1", "@mongodb-js/compass-instance": "^4.19.1", "@mongodb-js/compass-logging": "^1.2.6", + "@mongodb-js/compass-saved-aggregations-queries": "^1.20.1", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", "@mongodb-js/compass-sidebar": "^5.19.1", @@ -46774,10 +46776,13 @@ "version": "1.20.1", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/my-queries-storage": "^0.2.1", - "bson": "^6.0.0" + "bson": "^6.0.0", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.0.11", @@ -46798,9 +46803,7 @@ "depcheck": "^1.4.1", "eslint": "^7.25.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.0.14", "mocha": "^10.2.0", - "mongodb-data-service": "^22.15.1", "mongodb-instance-model": "^12.15.1", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", @@ -46813,10 +46816,13 @@ "xvfb-maybe": "^0.2.1" }, "peerDependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/my-queries-storage": "^0.2.1", "bson": "^6.0.0", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1", "react": "^17.0.2" } }, @@ -59227,6 +59233,7 @@ "@mongodb-js/compass-import-export": "^7.19.1", "@mongodb-js/compass-instance": "^4.19.1", "@mongodb-js/compass-logging": "^1.2.6", + "@mongodb-js/compass-saved-aggregations-queries": "^1.20.1", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", "@mongodb-js/compass-sidebar": "^5.19.1", @@ -59702,6 +59709,7 @@ "@mongodb-js/compass-saved-aggregations-queries": { "version": "file:packages/compass-saved-aggregations-queries", "requires": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/eslint-config-compass": "^1.0.11", diff --git a/packages/compass-app-stores/src/provider.ts b/packages/compass-app-stores/src/provider.ts index 7ec6775a3b1..4c3dcb53cde 100644 --- a/packages/compass-app-stores/src/provider.ts +++ b/packages/compass-app-stores/src/provider.ts @@ -12,3 +12,5 @@ export const mongoDBInstanceLocator = (): MongoDBInstance => { } return instance; }; + +export type { MongoDBInstance }; diff --git a/packages/compass-home/package.json b/packages/compass-home/package.json index 17dd36b5dcb..25b66b42a90 100644 --- a/packages/compass-home/package.json +++ b/packages/compass-home/package.json @@ -44,6 +44,7 @@ "@mongodb-js/compass-import-export": "^7.19.1", "@mongodb-js/compass-instance": "^4.19.1", "@mongodb-js/compass-logging": "^1.2.6", + "@mongodb-js/compass-saved-aggregations-queries": "^1.20.1", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", "@mongodb-js/compass-sidebar": "^5.19.1", @@ -65,6 +66,7 @@ "@mongodb-js/compass-import-export": "^7.19.1", "@mongodb-js/compass-instance": "^4.19.1", "@mongodb-js/compass-logging": "^1.2.6", + "@mongodb-js/compass-saved-aggregations-queries": "^1.20.1", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", "@mongodb-js/compass-sidebar": "^5.19.1", diff --git a/packages/compass-home/src/components/workspace-content.tsx b/packages/compass-home/src/components/workspace-content.tsx index 1db228ebeb5..3ea4624f362 100644 --- a/packages/compass-home/src/components/workspace-content.tsx +++ b/packages/compass-home/src/components/workspace-content.tsx @@ -7,6 +7,7 @@ import { import InstanceWorkspacePlugin, { InstanceTabsProvider, } from '@mongodb-js/compass-instance'; +import CompassSavedAggregationsQueriesPlugin from '@mongodb-js/compass-saved-aggregations-queries'; import type Namespace from '../types/namespace'; const EmptyComponent: React.FunctionComponent = () => null; @@ -30,7 +31,9 @@ const WorkspaceContent: React.FunctionComponent<{ namespace: Namespace }> = ({ } return ( - + ); diff --git a/packages/compass-saved-aggregations-queries/package.json b/packages/compass-saved-aggregations-queries/package.json index fc8b36cb8c5..174f46931b8 100644 --- a/packages/compass-saved-aggregations-queries/package.json +++ b/packages/compass-saved-aggregations-queries/package.json @@ -56,17 +56,23 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "peerDependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/my-queries-storage": "^0.2.1", "bson": "^6.0.0", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1", "react": "^17.0.2" }, "dependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/my-queries-storage": "^0.2.1", - "bson": "^6.0.0" + "bson": "^6.0.0", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.0.11", @@ -87,10 +93,7 @@ "depcheck": "^1.4.1", "eslint": "^7.25.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.0.14", "mocha": "^10.2.0", - "mongodb-data-service": "^22.15.1", - "mongodb-instance-model": "^12.15.1", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", "prettier": "^2.7.1", diff --git a/packages/compass-saved-aggregations-queries/src/components/aggregations-queries-list.tsx b/packages/compass-saved-aggregations-queries/src/components/aggregations-queries-list.tsx index 7bb402c7150..37c698860f4 100644 --- a/packages/compass-saved-aggregations-queries/src/components/aggregations-queries-list.tsx +++ b/packages/compass-saved-aggregations-queries/src/components/aggregations-queries-list.tsx @@ -1,18 +1,16 @@ import React, { useEffect, useCallback, useContext } from 'react'; import { connect } from 'react-redux'; -import type { ConnectedProps } from 'react-redux'; import { VirtualGrid, css, spacing, useSortControls, useSortedItems, - useEffectOnChange, } from '@mongodb-js/compass-components'; import { fetchItems } from '../stores/aggregations-queries-items'; import type { Item } from '../stores/aggregations-queries-items'; import { openSavedItem } from '../stores/open-item'; -import type { RootState } from '../stores/index'; +import type { RootState } from '../stores'; import { SavedItemCard, CARD_WIDTH, CARD_HEIGHT } from './saved-item-card'; import type { Action } from './saved-item-card'; import { NoSavedItems, NoSearchResults } from './empty-list-items'; @@ -22,9 +20,7 @@ import { useGridFilters, useFilteredItems } from '../hooks/use-grid-filters'; import { editItem } from '../stores/edit-item'; import { confirmDeleteItem } from '../stores/delete-item'; import { copyToClipboard } from '../stores/copy-to-clipboard'; -import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; - -const { track } = createLoggerAndTelemetry('COMPASS-MY-QUERIES-UI'); +import { useTrackOnChange } from '@mongodb-js/compass-logging/provider'; const sortBy: { name: keyof Item; label: string }[] = [ { @@ -73,6 +69,16 @@ const GridControls = () => { ); }; +type AggregationsQueriesListProps = { + loading: boolean; + items: Item[]; + onMount(): void; + onOpenItem(id: string): void; + onEditItem(id: string): void; + onDeleteItem(id: string): void; + onCopyToClipboard(id: string): void; +}; + const AggregationsQueriesList = ({ loading, items, @@ -98,17 +104,25 @@ const AggregationsQueriesList = ({ }) .map((x) => x.item); - useEffectOnChange(() => { - if (filters.database) { - track('My Queries Filter', { type: 'database' }); - } - }, filters.database); + useTrackOnChange( + 'COMPASS-MY-QUERIES-UI', + (track) => { + if (filters.database) { + track('My Queries Filter', { type: 'database' }); + } + }, + [filters.database] + ); - useEffectOnChange(() => { - if (filters.collection) { - track('My Queries Filter', { type: 'collection' }); - } - }, filters.collection); + useTrackOnChange( + 'COMPASS-MY-QUERIES-UI', + (track) => { + if (filters.collection) { + track('My Queries Filter', { type: 'collection' }); + } + }, + [filters.collection] + ); // If a user is searching, we disable the sort as // search results are sorted by match score @@ -116,12 +130,16 @@ const AggregationsQueriesList = ({ isDisabled: Boolean(search), }); - useEffectOnChange(() => { - track('My Queries Sort', { - sort_by: sortState.name, - order: sortState.order === 1 ? 'ascending' : 'descending', - }); - }, sortState); + useTrackOnChange( + 'COMPASS-MY-QUERIES-UI', + (track) => { + track('My Queries Sort', { + sort_by: sortState.name, + order: sortState.order === 1 ? 'ascending' : 'descending', + }); + }, + [sortState] + ); const sortedItems = useSortedItems(filteredItems, sortState); @@ -221,8 +239,4 @@ const mapDispatch = { onCopyToClipboard: copyToClipboard, }; -const connector = connect(mapState, mapDispatch); - -type AggregationsQueriesListProps = ConnectedProps; - -export default connector(AggregationsQueriesList); +export default connect(mapState, mapDispatch)(AggregationsQueriesList); diff --git a/packages/compass-saved-aggregations-queries/src/components/index.tsx b/packages/compass-saved-aggregations-queries/src/components/index.tsx deleted file mode 100644 index a13ee5c4b53..00000000000 --- a/packages/compass-saved-aggregations-queries/src/components/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import AggregationsQueriesList from './aggregations-queries-list'; -import { Provider } from 'react-redux'; -import store from '../stores/index'; - -const WithStore: React.FunctionComponent = () => { - return ( - // The way Compass requires to extend redux store is not compatible with - // redux types - // eslint-disable-next-line @typescript-eslint/no-explicit-any - - - - ); -}; - -export default WithStore; diff --git a/packages/compass-saved-aggregations-queries/src/hooks/use-grid-filters.tsx b/packages/compass-saved-aggregations-queries/src/hooks/use-grid-filters.tsx index dfaf04d1093..01dbdd1e3e8 100644 --- a/packages/compass-saved-aggregations-queries/src/hooks/use-grid-filters.tsx +++ b/packages/compass-saved-aggregations-queries/src/hooks/use-grid-filters.tsx @@ -8,9 +8,7 @@ import { Option, TextInput, } from '@mongodb-js/compass-components'; -import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; - -const { track } = createLoggerAndTelemetry('COMPASS-MY-QUERIES-UI'); +import { useLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import type { Item } from '../stores/aggregations-queries-items'; @@ -74,6 +72,7 @@ const FilterSelect: React.FunctionComponent<{ }; function useSearchFilter(): [React.ReactElement, string] { + const { track } = useLoggerAndTelemetry('COMPASS-MY-QUERIES-UI'); const [search, setSearch] = useState(''); const searchControls = useMemo(() => { return ( @@ -94,7 +93,7 @@ function useSearchFilter(): [React.ReactElement, string] { spellCheck={false} /> ); - }, [search]); + }, [search, track]); return [searchControls, search]; } diff --git a/packages/compass-saved-aggregations-queries/src/index.spec.ts b/packages/compass-saved-aggregations-queries/src/index.spec.ts deleted file mode 100644 index a4e12e68867..00000000000 --- a/packages/compass-saved-aggregations-queries/src/index.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { expect } from 'chai'; -import * as CompassPlugin from './index'; - -describe('Compass Plugin', function () { - it('exports activate, deactivate, and metadata', function () { - expect(CompassPlugin).to.have.property('activate'); - expect(CompassPlugin).to.have.property('deactivate'); - expect(CompassPlugin).to.have.property('metadata'); - }); -}); diff --git a/packages/compass-saved-aggregations-queries/src/components/index.test.tsx b/packages/compass-saved-aggregations-queries/src/index.spec.tsx similarity index 66% rename from packages/compass-saved-aggregations-queries/src/components/index.test.tsx rename to packages/compass-saved-aggregations-queries/src/index.spec.tsx index ced39d615cf..b7f049d912e 100644 --- a/packages/compass-saved-aggregations-queries/src/components/index.test.tsx +++ b/packages/compass-saved-aggregations-queries/src/index.spec.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import Sinon from 'sinon'; import { render, screen, @@ -8,45 +9,41 @@ import { } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { expect } from 'chai'; -import { FavoriteQueryStorage } from '@mongodb-js/my-queries-storage'; -import { PipelineStorage } from '@mongodb-js/my-queries-storage'; -import ConnectedList from './index'; -import * as store from './../stores'; - -import { queries, pipelines } from '../../test/fixtures'; -import Sinon from 'sinon'; - -const DATE = new Date('01/01/2020'); +import { queries, pipelines, DATE } from '../test/fixtures'; +import { MyQueriesPlugin } from '.'; describe('AggregationsQueriesList', function () { - let sandbox: Sinon.SinonSandbox; - let favoriteQueryStorageMock: Sinon.SinonMock; - let pipelineStorageMock: Sinon.SinonMock; - - beforeEach(function () { - sandbox = Sinon.createSandbox(); - favoriteQueryStorageMock = sandbox.mock(FavoriteQueryStorage.prototype); - pipelineStorageMock = sandbox.mock(PipelineStorage.prototype); - - sandbox.stub(store, 'queryStorage').returns(favoriteQueryStorageMock); - sandbox.stub(store, 'pipelineStorage').returns(pipelineStorageMock); + const sandbox = Sinon.createSandbox(); + const dataService = {} as any; + const instance = {} as any; + const queryStorage = { + loadAll: sandbox.stub().resolves([]), + updateAttributes: sandbox.stub().resolves({}), + }; + const pipelineStorage = { + loadAll: sandbox.stub().resolves([]), + updateAttributes: sandbox.stub().resolves({}), + }; + + const Plugin = MyQueriesPlugin.withMockServices({ + dataService, + instance, + queryStorage, + pipelineStorage, }); afterEach(function () { - sandbox.restore(); + sandbox.resetHistory(); cleanup(); }); it('should display no saved items when user has no saved queries/aggregations', async function () { - favoriteQueryStorageMock.expects('loadAll').resolves([]); - pipelineStorageMock.expects('loadAll').resolves([]); - - render(); + render(); expect(await screen.findByText('No saved queries yet.')).to.exist; }); it('should load queries and display them in the list', async function () { - favoriteQueryStorageMock.expects('loadAll').resolves([ + queryStorage.loadAll.resolves([ { _id: '123', _name: 'Query', @@ -54,14 +51,13 @@ describe('AggregationsQueriesList', function () { _dateSaved: DATE, }, ]); - pipelineStorageMock.expects('loadAll').resolves([]); - render(); + render(); expect(await screen.findByText('Query')).to.exist; }); it('should load aggregations and display them in the list', async function () { - favoriteQueryStorageMock.expects('loadAll').resolves([]); - pipelineStorageMock.expects('loadAll').resolves([ + queryStorage.loadAll.resolves([]); + pipelineStorage.loadAll.resolves([ { id: '123', name: 'Aggregation', @@ -69,13 +65,13 @@ describe('AggregationsQueriesList', function () { lastModified: 0, }, ]); - render(); + render(); expect(await screen.findByText('Aggregation')).to.exist; }); describe('copy to clipboard', function () { it('should copy query to the clipboard', async function () { - favoriteQueryStorageMock.expects('loadAll').resolves([ + queryStorage.loadAll.resolves([ { _id: '123', _name: 'My Query', @@ -85,9 +81,9 @@ describe('AggregationsQueriesList', function () { sort: { bar: -1 }, }, ]); - pipelineStorageMock.expects('loadAll').resolves([]); + pipelineStorage.loadAll.resolves([]); - render(); + render(); await waitFor(async () => await screen.findByText('My Query')); @@ -113,8 +109,8 @@ describe('AggregationsQueriesList', function () { }); it('should copy aggregation to the clipboard', async function () { - favoriteQueryStorageMock.expects('loadAll').resolves([]); - pipelineStorageMock.expects('loadAll').resolves([ + queryStorage.loadAll.resolves([]); + pipelineStorage.loadAll.resolves([ { id: '123', name: 'My Aggregation', @@ -130,7 +126,7 @@ describe('AggregationsQueriesList', function () { }, ]); - render(); + render(); await waitFor(async () => await screen.findByText('My Aggregation')); @@ -153,13 +149,12 @@ describe('AggregationsQueriesList', function () { context('with fixtures', function () { beforeEach(async function () { - favoriteQueryStorageMock - .expects('loadAll') - .resolves(queries.map((item) => (item as any).query)); - pipelineStorageMock - .expects('loadAll') - .resolves(pipelines.map((item) => (item as any).aggregation)); - render(); + queryStorage.loadAll.resolves(queries.map((item) => item.query)); + pipelineStorage.loadAll.resolves( + pipelines.map((item) => item.aggregation) + ); + + render(); // Wait for the items to "load" await screen.findByText(queries[0].name); @@ -169,28 +164,12 @@ describe('AggregationsQueriesList', function () { const { database, collection } = queries[0]; // select database - userEvent.click(screen.getByText('All databases'), undefined, { - skipPointerEventsCheck: true, - }); - userEvent.click( - screen.getByRole('option', { - name: database, - }), - undefined, - { skipPointerEventsCheck: true } - ); + userEvent.click(screen.getByRole('button', { name: 'All databases' })); + userEvent.click(screen.getByRole('option', { name: database })); // select collection - userEvent.click(screen.getByText('All collections'), undefined, { - skipPointerEventsCheck: true, - }); - userEvent.click( - screen.getByRole('option', { - name: collection, - }), - undefined, - { skipPointerEventsCheck: true } - ); + userEvent.click(screen.getByRole('button', { name: 'All collections' })); + userEvent.click(screen.getByRole('option', { name: collection })); const expectedItems = [...queries, ...pipelines].filter( (item) => item.database === database && item.collection === collection @@ -207,8 +186,8 @@ describe('AggregationsQueriesList', function () { const updatedName = 'the updated name'; // The first item is a query, so we are mocking that - favoriteQueryStorageMock.expects('updateAttributes').resolves({ - ...(item as any).query, + queryStorage.updateAttributes.resolves({ + ...item.query, _name: updatedName, }); @@ -229,12 +208,15 @@ describe('AggregationsQueriesList', function () { const title = new RegExp('rename query', 'i'); expect(within(modal).getByText(title), 'show title').to.exist; - const nameInput = within(modal).getByRole('textbox', { + const nameInput = within(modal).getByRole('textbox', { name: /name/i, }); expect(nameInput, 'show name input').to.exist; - expect(nameInput.value, 'input with item name').to.equal(item.name); + expect(nameInput, 'input with item name').to.have.property( + 'value', + item.name + ); expect( within(modal).getByRole('button', { diff --git a/packages/compass-saved-aggregations-queries/src/index.ts b/packages/compass-saved-aggregations-queries/src/index.ts index a492db3cc5a..56f0c969736 100644 --- a/packages/compass-saved-aggregations-queries/src/index.ts +++ b/packages/compass-saved-aggregations-queries/src/index.ts @@ -1,23 +1,53 @@ -import type AppRegistry from 'hadron-app-registry'; -import store from './stores/index'; -import Component from './components/index'; +import { registerHadronPlugin } from 'hadron-app-registry'; +import { dataServiceLocator } from 'mongodb-data-service/provider'; +import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider'; +import { createLoggerAndTelemetryLocator } from '@mongodb-js/compass-logging/provider'; +import type { DataService } from 'mongodb-data-service'; +import { activatePlugin } from './stores'; +import AggregationsQueriesList from './components/aggregations-queries-list'; +import type { + PipelineStorage, + RecentQueryStorage, +} from '@mongodb-js/my-queries-storage'; -const role = { - component: Component, - name: 'My Queries', - order: 1, -}; - -function activate(appRegistry: AppRegistry): void { - appRegistry.registerStore('App.AggregationsQueriesListStore', store); - appRegistry.registerRole('Instance.Tab', role); +function activate(): void { + // noop } -function deactivate(appRegistry: AppRegistry): void { - appRegistry.deregisterStore('App.AggregationsQueriesListStore'); - appRegistry.deregisterRole('Instance.Tab', role); +function deactivate(): void { + // noop } -export { activate, deactivate }; +const serviceLocators = { + dataService: dataServiceLocator as typeof dataServiceLocator< + // Getting passed to the mongodb instance so hard to be more explicit + // about used methods + keyof DataService + >, + instance: mongoDBInstanceLocator, + logger: createLoggerAndTelemetryLocator('COMPASS-MY-QUERIES-UI'), +}; +export const MyQueriesPlugin = registerHadronPlugin< + React.ComponentProps, + typeof serviceLocators & { + queryStorage?: () => RecentQueryStorage; + pipelineStorage?: () => PipelineStorage; + } +>( + { + name: 'MyQueries', + component: AggregationsQueriesList, + activate: activatePlugin, + }, + serviceLocators +); + +const InstanceTab = { + name: 'My Queries', + component: MyQueriesPlugin, +}; + +export default InstanceTab; +export { activate, deactivate }; export { default as metadata } from '../package.json'; diff --git a/packages/compass-saved-aggregations-queries/src/stores/app-registry.ts b/packages/compass-saved-aggregations-queries/src/stores/app-registry.ts deleted file mode 100644 index f8245d9d70d..00000000000 --- a/packages/compass-saved-aggregations-queries/src/stores/app-registry.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type AppRegistry from 'hadron-app-registry'; -import type { ActionCreator, Reducer } from 'redux'; - -export type State = AppRegistry | null; - -export enum ActionTypes { - SetAppRegistry = 'compass-saved-aggregations-queries/setAppRegistry', -} - -type SetAppRegistryAction = { - type: ActionTypes.SetAppRegistry; - appRegistry: AppRegistry; -}; - -export type Actions = SetAppRegistryAction; - -const INITIAL_STATE = null; - -const reducer: Reducer = (state = INITIAL_STATE, action) => { - if (action.type === ActionTypes.SetAppRegistry) { - return action.appRegistry; - } - return state; -}; - -export const setAppRegistry: ActionCreator = ( - appRegistry: AppRegistry -) => { - return { type: ActionTypes.SetAppRegistry, appRegistry }; -}; - -export default reducer; diff --git a/packages/compass-saved-aggregations-queries/src/stores/copy-to-clipboard.ts b/packages/compass-saved-aggregations-queries/src/stores/copy-to-clipboard.ts index 758e0a821f8..3c3d682149d 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/copy-to-clipboard.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/copy-to-clipboard.ts @@ -1,11 +1,7 @@ -import type { RootActions, RootState } from './index'; -import type { ThunkAction } from 'redux-thunk'; +import type { SavedQueryAggregationThunkAction } from './index'; import { EJSON } from 'bson'; -import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; import { type FavoriteQuery } from '@mongodb-js/my-queries-storage'; -const { track } = createLoggerAndTelemetry('COMPASS-MY-QUERIES-UI'); - function formatQuery(query: FavoriteQuery) { const { collation, filter, limit, project, skip, sort } = query; return EJSON.stringify( @@ -24,8 +20,8 @@ function formatQuery(query: FavoriteQuery) { export function copyToClipboard( id: string -): ThunkAction, RootState, void, RootActions> { - return async (_dispatch, getState) => { +): SavedQueryAggregationThunkAction> { + return async (_dispatch, getState, { logger: { track } }) => { const { savedItems: { items }, } = getState(); diff --git a/packages/compass-saved-aggregations-queries/src/stores/data-service.ts b/packages/compass-saved-aggregations-queries/src/stores/data-service.ts deleted file mode 100644 index a379228bdf3..00000000000 --- a/packages/compass-saved-aggregations-queries/src/stores/data-service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Reducer } from 'redux'; -import type { DataService } from 'mongodb-data-service'; - -export type State = DataService | null; - -export enum ActionTypes { - SetDataService = 'compass-saved-aggregations-queries/setDataService', - ResetDataService = 'compass-saved-aggregations-queries/resetDataService', -} - -type SetDataServiceAction = { - type: ActionTypes.SetDataService; - dataService: DataService; -}; - -type ResetDataServiceAction = { type: ActionTypes.ResetDataService }; - -export type Actions = SetDataServiceAction | ResetDataServiceAction; - -export function setDataService(dataService: DataService): SetDataServiceAction { - return { type: ActionTypes.SetDataService, dataService }; -} - -export function resetDataService(): ResetDataServiceAction { - return { type: ActionTypes.ResetDataService }; -} - -const reducer: Reducer = (state = null, action) => { - switch (action.type) { - case ActionTypes.SetDataService: - return action.dataService; - case ActionTypes.ResetDataService: - return null; - default: - return state; - } -}; - -export default reducer; diff --git a/packages/compass-saved-aggregations-queries/src/stores/delete-item.ts b/packages/compass-saved-aggregations-queries/src/stores/delete-item.ts index 05259d32de6..222f7ed87cf 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/delete-item.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/delete-item.ts @@ -1,12 +1,9 @@ import type { SavedQueryAggregationThunkAction } from '.'; -import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; import { showConfirmation, ConfirmationModalVariant, } from '@mongodb-js/compass-components'; -const { track } = createLoggerAndTelemetry('COMPASS-MY-QUERIES-UI'); - export enum ActionTypes { DeleteItemConfirm = 'compass-saved-aggregations-queries/deleteItemConfirm', } @@ -21,7 +18,11 @@ export type Actions = DeleteItemConfirmAction; export const confirmDeleteItem = ( id: string ): SavedQueryAggregationThunkAction, DeleteItemConfirmAction> => { - return async (dispatch, getState, { pipelineStorage, queryStorage }) => { + return async ( + dispatch, + getState, + { pipelineStorage, queryStorage, logger: { track } } + ) => { const { savedItems: { items }, } = getState(); diff --git a/packages/compass-saved-aggregations-queries/src/stores/edit-item.ts b/packages/compass-saved-aggregations-queries/src/stores/edit-item.ts index 711b94f6a62..26ec3155298 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/edit-item.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/edit-item.ts @@ -41,7 +41,7 @@ export type Actions = | EditItemCancelledAction | EditItemUpdatedAction; -const reducer: Reducer = (state = INITIAL_STATE, action) => { +const reducer: Reducer = (state = INITIAL_STATE, action) => { switch (action.type) { case ActionTypes.EditItemClicked: return { @@ -86,10 +86,7 @@ export const updateItem = _name: attributes.name, _dateModified: new Date(), }) - : ((await pipelineStorage.updateAttributes( - id, - attributes - )) as SavedPipeline); + : await pipelineStorage.updateAttributes(id, attributes); dispatch({ type: ActionTypes.EditItemUpdated, diff --git a/packages/compass-saved-aggregations-queries/src/stores/index.ts b/packages/compass-saved-aggregations-queries/src/stores/index.ts index f7616a5714b..4978a55a46e 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/index.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/index.ts @@ -1,84 +1,78 @@ import type AppRegistry from 'hadron-app-registry'; import { createStore, applyMiddleware, combineReducers } from 'redux'; -import type { Store, AnyAction, Action } from 'redux'; +import type { AnyAction, Action } from 'redux'; import thunk from 'redux-thunk'; import type { ThunkAction } from 'redux-thunk'; import itemsReducer from './aggregations-queries-items'; -import instanceReducer, { setInstance, resetInstance } from './instance'; -import dataServiceReducer, { - setDataService, - resetDataService, -} from './data-service'; import openItemReducer from './open-item'; import editItemReducer from './edit-item'; -import appRegistryReducer, { setAppRegistry } from './app-registry'; - import { FavoriteQueryStorage } from '@mongodb-js/my-queries-storage'; import { PipelineStorage } from '@mongodb-js/my-queries-storage'; +import type { DataService } from 'mongodb-data-service'; +import type { MongoDBInstance } from '@mongodb-js/compass-app-stores/provider'; +import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging'; + +type MyQueriesServices = { + dataService: DataService; + instance: MongoDBInstance; + globalAppRegistry: AppRegistry; + logger: LoggerAndTelemetry; +}; -// Exporting so that they can be stubed/spied in tests -export const queryStorage = new FavoriteQueryStorage(); -export const pipelineStorage = new PipelineStorage(); - -const _store = createStore( - combineReducers({ - savedItems: itemsReducer, - instance: instanceReducer, - dataService: dataServiceReducer, - openItem: openItemReducer, - editItem: editItemReducer, - appRegistry: appRegistryReducer, - }), - applyMiddleware( - thunk.withExtraArgument({ - pipelineStorage, - queryStorage, - }) - ) -); - -export type SavedQueryAggregationExtraArgs = { +// TODO(COMPASS-7411): should also be service injected, this type will merge +// with the one above +type Storages = { pipelineStorage: PipelineStorage; queryStorage: FavoriteQueryStorage; }; +function configureStore({ + globalAppRegistry, + dataService, + instance, + logger, + pipelineStorage = new PipelineStorage(), + queryStorage = new FavoriteQueryStorage(), +}: MyQueriesServices & Partial) { + return createStore( + combineReducers({ + savedItems: itemsReducer, + openItem: openItemReducer, + editItem: editItemReducer, + }), + applyMiddleware( + thunk.withExtraArgument({ + globalAppRegistry, + dataService, + instance, + logger, + pipelineStorage, + queryStorage, + }) + ) + ); +} + +export type RootState = ReturnType< + ReturnType['getState'] +>; + +type SavedQueryAggregationExtraArgs = MyQueriesServices & Storages; + export type SavedQueryAggregationThunkAction< R, A extends Action = AnyAction > = ThunkAction; -type StoreActions = T extends Store ? A : never; - -type StoreState = T extends Store ? S : never; - -export type RootActions = StoreActions; - -export type RootState = StoreState; - -const store = Object.assign(_store, { - onActivated(appRegistry: AppRegistry) { - store.dispatch(setAppRegistry(appRegistry)); - - appRegistry.on('data-service-connected', (err, dataService) => { - if (err) { - return; - } - - store.dispatch(setDataService(dataService)); - }); - - appRegistry.on('data-service-disconnected', () => { - store.dispatch(resetDataService()); - }); - - appRegistry.on('instance-created', ({ instance }) => { - store.dispatch(setInstance(instance)); - }); - - appRegistry.on('instance-destroyed', () => { - store.dispatch(resetInstance()); - }); - }, -}); - -export default store; +export function activatePlugin( + _: unknown, + services: MyQueriesServices & Partial +) { + const store = configureStore(services); + return { + store, + deactivate() { + // noop, no subscriptions in this plugin + }, + }; +} diff --git a/packages/compass-saved-aggregations-queries/src/stores/instance.ts b/packages/compass-saved-aggregations-queries/src/stores/instance.ts deleted file mode 100644 index 4c63dd4ea22..00000000000 --- a/packages/compass-saved-aggregations-queries/src/stores/instance.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Reducer } from 'redux'; -import type { MongoDBInstance } from 'mongodb-instance-model'; - -export type State = MongoDBInstance | null; - -export enum ActionTypes { - SetInstance = 'compass-saved-aggregations-queries/setInstance', - ResetInstance = 'compass-saved-aggregations-queries/resetInstance', -} - -type SetInstanceAction = { - type: ActionTypes.SetInstance; - instance: MongoDBInstance; -}; - -type ResetInstanceAction = { type: ActionTypes.ResetInstance }; - -export type Actions = SetInstanceAction | ResetInstanceAction; - -export function setInstance(instance: MongoDBInstance): SetInstanceAction { - return { type: ActionTypes.SetInstance, instance }; -} - -export function resetInstance(): ResetInstanceAction { - return { type: ActionTypes.ResetInstance }; -} - -const reducer: Reducer = (state = null, action) => { - switch (action.type) { - case ActionTypes.SetInstance: - return action.instance; - case ActionTypes.ResetInstance: - return null; - default: - return state; - } -}; - -export default reducer; diff --git a/packages/compass-saved-aggregations-queries/src/stores/open-item.ts b/packages/compass-saved-aggregations-queries/src/stores/open-item.ts index 40d43b8a955..e3d02405257 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/open-item.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/open-item.ts @@ -1,10 +1,6 @@ import type { ActionCreator, Reducer } from 'redux'; -import type { ThunkAction } from 'redux-thunk'; -import type { RootState } from '.'; +import type { SavedQueryAggregationThunkAction } from '.'; import type { Item } from './aggregations-queries-items'; -import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; - -const { track } = createLoggerAndTelemetry('COMPASS-MY-QUERIES-UI'); export type Status = 'initial' | 'fetching' | 'error' | 'ready'; @@ -109,7 +105,7 @@ export type Actions = | LoadCollectionsErrorAction | LoadCollectionsSuccessAction; -const reducer: Reducer = (state = INITIAL_STATE, action) => { +const reducer: Reducer = (state = INITIAL_STATE, action) => { switch (action.type) { case ActionTypes.OpenModal: return { @@ -175,16 +171,10 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { }; const openModal = - (selectedItem: Item): ThunkAction => - async (dispatch, getState) => { + (selectedItem: Item): SavedQueryAggregationThunkAction> => + async (dispatch, _getState, { instance, dataService }) => { dispatch({ type: ActionTypes.OpenModal, selectedItem }); - const { instance, dataService } = getState(); - - if (!instance || !dataService) { - return; - } - dispatch({ type: ActionTypes.LoadDatabases }); try { @@ -207,14 +197,8 @@ const openItem = item: Item, database: string, collection: string - ): ThunkAction => - (dispatch, getState) => { - const { appRegistry } = getState(); - - if (!appRegistry) { - return; - } - + ): SavedQueryAggregationThunkAction => + (_dispatch, _getState, { globalAppRegistry, logger: { track } }) => { track( item.type === 'aggregation' ? 'Aggregation Opened' @@ -225,7 +209,7 @@ const openItem = } ); - appRegistry.emit('my-queries-open-saved-item', { + globalAppRegistry.emit('my-queries-open-saved-item', { ns: `${database}.${collection}`, aggregation: item.type === 'aggregation' ? item.aggregation : null, query: item.type === 'query' ? item.query : null, @@ -233,11 +217,9 @@ const openItem = }; export const openSavedItem = - (id: string): ThunkAction => - async (dispatch, getState) => { + (id: string): SavedQueryAggregationThunkAction> => + async (dispatch, getState, { instance, dataService }) => { const { - instance, - dataService, savedItems: { items }, } = getState(); @@ -247,10 +229,6 @@ export const openSavedItem = return; } - if (!instance || !dataService) { - return; - } - const { database, collection } = item; const coll = await instance.getNamespace({ @@ -260,7 +238,7 @@ export const openSavedItem = }); if (!coll) { - dispatch(openModal(item)); + void dispatch(openModal(item)); return; } @@ -268,7 +246,7 @@ export const openSavedItem = }; export const openSelectedItem = - (): ThunkAction => (dispatch, getState) => { + (): SavedQueryAggregationThunkAction => (dispatch, getState) => { const { openItem: { selectedItem, selectedDatabase, selectedCollection }, } = getState(); @@ -282,18 +260,12 @@ export const openSelectedItem = }; export const selectDatabase = - (database: string): ThunkAction => - async (dispatch, getState) => { + (database: string): SavedQueryAggregationThunkAction> => + async (dispatch, getState, { instance, dataService }) => { const { - instance, - dataService, openItem: { selectedDatabase }, } = getState(); - if (!instance || !dataService) { - return; - } - if (database === selectedDatabase) { return; } diff --git a/packages/compass-saved-aggregations-queries/test/fixtures.ts b/packages/compass-saved-aggregations-queries/test/fixtures.ts index e726cfa37ac..4525bdec8c7 100644 --- a/packages/compass-saved-aggregations-queries/test/fixtures.ts +++ b/packages/compass-saved-aggregations-queries/test/fixtures.ts @@ -1,13 +1,13 @@ -import type { Item } from '../src/stores/aggregations-queries-items'; -const DATE = new Date('01/01/2020'); -export const queries: Item[] = [ +export const DATE = new Date('01/01/2020'); + +export const queries = [ { id: '1234', name: 'spaces in berlin', database: 'airbnb', collection: 'listings', lastModified: 11, - type: 'query', + type: 'query' as const, query: { _id: '1234', _name: 'spaces in berlin', @@ -39,7 +39,7 @@ export const queries: Item[] = [ database: 'airbnb', collection: 'listings', lastModified: 12, - type: 'query', + type: 'query' as const, query: { _id: '5678', _name: 'best spaces in berlin', @@ -72,7 +72,7 @@ export const queries: Item[] = [ database: 'airbnb', collection: 'hosts', lastModified: 13, - type: 'query', + type: 'query' as const, query: { _id: '9012', _name: 'best hosts in berlin', @@ -99,13 +99,13 @@ export const queries: Item[] = [ }, ]; -export const pipelines: Item[] = [ +export const pipelines = [ { id: '61b753fdce2a0a1d7a32ae1d', name: 'Demo', database: 'airbnb', collection: 'listings', - type: 'aggregation', + type: 'aggregation' as const, lastModified: 21, aggregation: { namespace: 'airbnb.listings', diff --git a/packages/hadron-app-registry/src/register-plugin.tsx b/packages/hadron-app-registry/src/register-plugin.tsx index cd825e31c99..4a3c958c83b 100644 --- a/packages/hadron-app-registry/src/register-plugin.tsx +++ b/packages/hadron-app-registry/src/register-plugin.tsx @@ -234,7 +234,7 @@ export function registerHadronPlugin< ...mockServices } = mocks; const mockServiceLocators = Object.fromEntries( - Object.keys(services ?? {}).map((s) => { + Object.keys({ ...services, ...mockServices }).map((s) => { return [s, mockServices[s] ? () => mockServices[s] : services?.[s]]; }) ) as unknown as S;