diff --git a/package-lock.json b/package-lock.json index d8c564b0eab..c102a834a70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70008,10 +70008,8 @@ "hadron-app": "^5.0.0", "hadron-app-registry": "^9.0.0", "lodash.contains": "^2.4.3", - "lodash.isundefined": "^3.0.1", "lodash.map": "^4.6.0", "lodash.max": "^4.0.1", - "lodash.pick": "^4.4.0", "mocha": "^8.4.0", "mongodb": "^4.6.0", "mongodb-data-service": "^22.0.0", @@ -107552,10 +107550,8 @@ "hadron-app-registry": "^9.0.0", "hadron-react-components": "^6.0.0", "lodash.contains": "^2.4.3", - "lodash.isundefined": "^3.0.1", "lodash.map": "^4.6.0", "lodash.max": "^4.0.1", - "lodash.pick": "^4.4.0", "mocha": "^8.4.0", "mongodb": "^4.6.0", "mongodb-data-service": "^22.0.0", diff --git a/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx index 0c24e10db7e..5f539d7a676 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx @@ -147,7 +147,8 @@ describe('PipelineExplain', function () { name: /ascending index/i, // host_id index direction 1 }) ).to.exist; - expect(within(indexContent1).getByText(/location \(2dsphere\)/i)).to.exist; + expect(within(indexContent1).getByText(/location/i)).to.exist; + expect(within(indexContent1).getByText(/\(2dsphere\)/i)).to.exist; // Toggle second accordian userEvent.click( @@ -164,6 +165,7 @@ describe('PipelineExplain', function () { name: /descending index/i, // city_id index direction -1 }) ).to.exist; - expect(within(indexContent2).getByText(/title \(text\)/i)).to.exist; + expect(within(indexContent2).getByText(/title/i)).to.exist; + expect(within(indexContent2).getByText(/\(text\)/i)).to.exist; }); }); diff --git a/packages/compass-components/src/components/index-icon.tsx b/packages/compass-components/src/components/index-icon.tsx index 5ad63b3c392..3f66ad02155 100644 --- a/packages/compass-components/src/components/index-icon.tsx +++ b/packages/compass-components/src/components/index-icon.tsx @@ -9,7 +9,7 @@ const IndexIcon = ({ direction }: { direction: IndexDirection }) => { ) : direction === -1 ? ( ) : ( - <>({String(direction)}) + ({String(direction)}) ); }; diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index a33bc7475f5..09d0c1e2cd3 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -770,10 +770,18 @@ export const ExplainDocumentsReturnedSummary = '[data-test-id="documents-returned-summary"]'; // Indexes tab -export const IndexList = '[data-test-id="index-list"]'; -export const IndexComponent = '[data-test-id="index-list"] tr'; -export const IndexFieldName = '[data-testid="index-field-name"]'; -export const IndexFieldType = '[data-testid="index-field-type"]'; +export const IndexList = '[data-testid="indexes-list"]'; +export const IndexComponent = (name: string): string => { + return `[data-testid="index-row-${name}"]`; +}; +export const IndexFieldName = '[data-testid="index-name-field"]'; +export const IndexFieldType = '[data-testid="index-type-field"]'; +export const IndexToggleOptions = + '[data-testid="create-index-modal-toggle-options"]'; +export const IndexToggleIsWildcard = + '[data-testid="create-index-modal-use-wildcard-checkbox-fieldset"] #create-index-modal-use-wildcard-checkbox-label'; +export const IndexWildcardProjectionEditor = + '[data-testid="create-index-modal-use-wildcard-checkbox-fieldset"] .ace_editor'; export const CreateIndexButton = '[data-testid="open-create-index-modal-button"]'; @@ -791,13 +799,6 @@ export const CreateIndexModalFieldTypeSelectMenu = (idx: number): string => { return `[data-testid="create-index-fields-type-${idx}"] #create-index-fields-type-select-${idx}-menu`; }; -export const IndexToggleOptions = - '[data-testid="create-index-modal-toggle-options"]'; -export const IndexToggleIsWildcard = - '[data-testid="create-index-modal-use-wildcard-checkbox-fieldset"] #create-index-modal-use-wildcard-checkbox-label'; -export const IndexWildcardProjectionEditor = - '[data-testid="create-index-modal-use-wildcard-checkbox-fieldset"] .ace_editor'; - export const CreateIndexErrorMessage = `${CreateIndexModal} [role="alert"]`; export const CreateIndexConfirmButton = `${CreateIndexModal} [role=dialog] > div:nth-child(2) button:first-child`; export const CreateIndexCancelButton = `${CreateIndexModal} [role=dialog] > div:nth-child(2) button:last-child`; @@ -808,13 +809,7 @@ export const DropIndexModalConfirmName = export const DropIndexModalConfirmButton = '[data-testid="drop_index_modal"] [role=dialog] > div:nth-child(2) button:first-child'; -export const indexComponent = (indexName: string): string => { - return `[data-test-id="index-component-${indexName}"]`; -}; - -export const dropIndexButton = (indexName: string): string => { - return `[data-testid="drop-index-button-${indexName}"]`; -}; +export const DropIndexButton = '[data-testid="drop-index-button"]'; // Validation tab export const AddRuleButton = '[data-test-id="add-rule-button"]'; diff --git a/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts b/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts index 0ebc8673777..799f225c552 100644 --- a/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts @@ -41,10 +41,12 @@ describe('Collection indexes tab', function () { const element = await browser.$(Selectors.IndexList); await element.waitForDisplayed(); - const indexes = await browser.$$(Selectors.IndexComponent); + const indexes = await browser.$$(Selectors.IndexComponent('_id_')); expect(indexes).to.have.lengthOf(1); - const indexFieldNameElement = await browser.$(Selectors.IndexFieldName); + const indexFieldNameElement = await browser.$( + `${Selectors.IndexComponent('_id_')} ${Selectors.IndexFieldName}` + ); expect(await indexFieldNameElement.getText()).to.equal('_id_'); }); @@ -81,10 +83,15 @@ describe('Collection indexes tab', function () { await createModal.waitForDisplayed({ reverse: true }); - const indexComponent = await browser.$(Selectors.indexComponent('i_text')); + const indexComponentSelector = Selectors.IndexComponent('i_text'); + + const indexComponent = await browser.$(indexComponentSelector); await indexComponent.waitForDisplayed(); - await browser.clickVisible(Selectors.dropIndexButton('i_text')); + await browser.hover(indexComponentSelector); + await browser.clickVisible( + `${indexComponentSelector} ${Selectors.DropIndexButton}` + ); const dropModal = await browser.$(Selectors.DropIndexModal); await dropModal.waitForDisplayed(); @@ -157,10 +164,10 @@ describe('Collection indexes tab', function () { await createModal.waitForDisplayed({ reverse: true }); - const indexComponent = await browser.$(Selectors.indexComponent('$**_1')); + const indexComponent = await browser.$(Selectors.IndexComponent('$**_1')); await indexComponent.waitForDisplayed(); - const indexFieldTypeSelector = `${Selectors.indexComponent('$**_1')} ${ + const indexFieldTypeSelector = `${Selectors.IndexComponent('$**_1')} ${ Selectors.IndexFieldType }`; const indexFieldTypeElement = await browser.$(indexFieldTypeSelector); @@ -212,11 +219,15 @@ describe('Collection indexes tab', function () { await createModal.waitForDisplayed({ reverse: true }); const indexComponent = await browser.$( - Selectors.indexComponent('columnstore') + Selectors.IndexComponent('columnstore') ); await indexComponent.waitForDisplayed(); - await browser.clickVisible(Selectors.dropIndexButton('columnstore')); + await browser.clickVisible( + `${Selectors.IndexComponent('columnstore')} ${ + Selectors.DropIndexButton + }` + ); const dropModal = await browser.$(Selectors.DropIndexModal); await dropModal.waitForDisplayed(); diff --git a/packages/compass-e2e-tests/tests/database-collections-tab.test.ts b/packages/compass-e2e-tests/tests/database-collections-tab.test.ts index f201e44b2bb..227459592ee 100644 --- a/packages/compass-e2e-tests/tests/database-collections-tab.test.ts +++ b/packages/compass-e2e-tests/tests/database-collections-tab.test.ts @@ -250,7 +250,7 @@ describe('Database collections tab', function () { await browser.navigateToCollectionTab('test', collectionName, 'Indexes'); - const typeElementSelector = `${Selectors.indexComponent(indexName)} ${ + const typeElementSelector = `${Selectors.IndexComponent(indexName)} ${ Selectors.IndexFieldType }`; const typeElement = await browser.$(typeElementSelector); diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 1ae92b2fbc0..0abd12a4946 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -80,10 +80,8 @@ "hadron-app": "^5.0.0", "hadron-app-registry": "^9.0.0", "lodash.contains": "^2.4.3", - "lodash.isundefined": "^3.0.1", "lodash.map": "^4.6.0", "lodash.max": "^4.0.1", - "lodash.pick": "^4.4.0", "mocha": "^8.4.0", "mongodb": "^4.6.0", "mongodb-data-service": "^22.0.0", diff --git a/packages/compass-indexes/src/components/drop-column/drop-column.jsx b/packages/compass-indexes/src/components/drop-column/drop-column.jsx deleted file mode 100644 index 769ba93bd91..00000000000 --- a/packages/compass-indexes/src/components/drop-column/drop-column.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { IconButton, Icon } from '@mongodb-js/compass-components'; - -/** - * Component for the drop column. - */ -class DropColumn extends PureComponent { - static displayName = 'DropColumn'; - - static propTypes = { - indexName: PropTypes.string.isRequired, - isReadonly: PropTypes.bool.isRequired, - isWritable: PropTypes.bool.isRequired, - localAppRegistry: PropTypes.object.isRequired, - }; - - /** - * Show drop index modal when drop button is clicked. - * - * @param {Object} evt - The click event. - */ - clickDropHandler(evt) { - evt.preventDefault(); - evt.stopPropagation(); - this.props.localAppRegistry.emit( - 'toggle-drop-index-modal', - true, - this.props.indexName - ); - } - - /** - * Is the index droppable? - * - * @returns {Boolean} If the index can be dropped. - */ - isDroppable() { - return ( - this.props.isWritable && - this.props.indexName !== '_id_' && - !this.props.isReadonly - ); - } - - /** - * Render the drop column. - * - * @returns {React.Component} The drop column. - */ - render() { - return ( - - {this.isDroppable() ? ( - - - - ) : null} - - ); - } -} - -export default DropColumn; diff --git a/packages/compass-indexes/src/components/drop-column/index.js b/packages/compass-indexes/src/components/drop-column/index.js deleted file mode 100644 index f699f609125..00000000000 --- a/packages/compass-indexes/src/components/drop-column/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import DropColumn from './drop-column'; -export default DropColumn; diff --git a/packages/compass-indexes/src/components/index-component/index-component.jsx b/packages/compass-indexes/src/components/index-component/index-component.jsx deleted file mode 100644 index 6b7f5bf6188..00000000000 --- a/packages/compass-indexes/src/components/index-component/index-component.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import NameColumn from '../name-column'; -import TypeColumn from '../type-column'; -import SizeColumn from '../size-column'; -import UsageColumn from '../usage-column'; -import PropertyColumn from '../property-column'; -import DropColumn from '../drop-column'; - -import { css, spacing, uiColors } from '@mongodb-js/compass-components'; - -const containerStyles = css({ - backgroundColor: uiColors.white, - boxShadow: '0 2px 10px 0 rgba(0, 0, 0, 0.075)', - td: { - padding: spacing[3], - }, -}); - -/** - * Component for the index. - */ -class IndexComponent extends PureComponent { - static displayName = 'IndexComponent'; - static propTypes = { - index: PropTypes.object.isRequired, - isReadonly: PropTypes.bool.isRequired, - isWritable: PropTypes.bool.isRequired, - localAppRegistry: PropTypes.object.isRequired, - nameChanged: PropTypes.func.isRequired, - openLink: PropTypes.func.isRequired, - }; - - /** - * Render the index. - * - * @returns {React.Component} The index. - */ - render() { - return ( - - - - - - - - - ); - } -} - -export default IndexComponent; diff --git a/packages/compass-indexes/src/components/index-component/index-component.spec.jsx b/packages/compass-indexes/src/components/index-component/index-component.spec.jsx deleted file mode 100644 index 47832a218a5..00000000000 --- a/packages/compass-indexes/src/components/index-component/index-component.spec.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { expect } from 'chai'; -import AppRegistry from 'hadron-app-registry'; - -import IndexComponent from '../index-component'; - -import NameColumn from '../name-column'; -import TypeColumn from '../type-column'; -import SizeColumn from '../size-column'; -import UsageColumn from '../usage-column'; -import PropertyColumn from '../property-column'; -import DropColumn from '../drop-column'; - -const index = { - name: 'a', - type: 'regular', - fields: { serialize: () => [] }, - size: 10, - relativeSize: 10, - properties: [], -}; - -describe('index-component [Component]', function () { - const localAppRegistry = new AppRegistry(); - let component; - - describe('render', function () { - beforeEach(function () { - component = mount( - {}} - nameChanged={() => {}} - openLink={() => {}} - index={index} - /> - ); - }); - afterEach(function () { - component = null; - }); - it('renders the type column', function () { - expect(component.find(TypeColumn)).to.be.present(); - }); - it('renders the size column', function () { - expect(component.find(SizeColumn)).to.be.present(); - }); - it('renders the usage column', function () { - expect(component.find(UsageColumn)).to.be.present(); - }); - it('renders the property column', function () { - expect(component.find(PropertyColumn)).to.be.present(); - }); - it('renders the drop column', function () { - expect(component.find(DropColumn)).to.be.present(); - }); - it('renders the name column', function () { - expect(component.find(NameColumn)).to.be.present(); - }); - }); -}); diff --git a/packages/compass-indexes/src/components/index-component/index.js b/packages/compass-indexes/src/components/index-component/index.js deleted file mode 100644 index 3fb0007ed3c..00000000000 --- a/packages/compass-indexes/src/components/index-component/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import IndexComponent from './index-component'; -export default IndexComponent; diff --git a/packages/compass-indexes/src/components/index-header-column/index-header-column.jsx b/packages/compass-indexes/src/components/index-header-column/index-header-column.jsx deleted file mode 100644 index 698032ad0ca..00000000000 --- a/packages/compass-indexes/src/components/index-header-column/index-header-column.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { css, cx, uiColors, spacing } from '@mongodb-js/compass-components'; - -import { DEFAULT, DESC, ASC } from '../../modules/indexes'; - -const headerStyles = css({ - paddingTop: spacing[2], - paddingBottom: spacing[2], - paddingRight: spacing[4], - paddingLeft: spacing[4], -}); - -const sortStyles = css({ - display: 'inline-block', - color: uiColors.gray.base, -}); - -/** - * Component for an index header column. - */ -class IndexHeaderColumn extends PureComponent { - static displayName = 'IndexHeaderColumn'; - static propTypes = { - indexes: PropTypes.array.isRequired, - sortOrder: PropTypes.string.isRequired, - sortColumn: PropTypes.string.isRequired, - dataTestId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - sortIndexes: PropTypes.func.isRequired, - }; - - /** - * Handle the index sort click. - * - * @param {Event} evt - The event. - */ - handleIndexSort(evt) { - evt.preventDefault(); - evt.stopPropagation(); - let order; - if (this.props.sortColumn === this.props.name) { - order = this.props.sortOrder === ASC ? DESC : ASC; - } else { - order = this.props.sortColumn === DEFAULT ? ASC : DESC; - } - this.props.sortIndexes(this.props.indexes, this.props.name, order); - } - - /** - * Render the index header column. - * - * @returns {React.Component} The index header column. - */ - render() { - return ( - - {this.props.name} - {this.props.sortColumn === this.props.name && ( - - )} - - ); - } -} - -export default IndexHeaderColumn; diff --git a/packages/compass-indexes/src/components/index-header-column/index-header-column.spec.jsx b/packages/compass-indexes/src/components/index-header-column/index-header-column.spec.jsx deleted file mode 100644 index 6ce69ad24ba..00000000000 --- a/packages/compass-indexes/src/components/index-header-column/index-header-column.spec.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { expect } from 'chai'; -import sinon from 'sinon'; - -import IndexHeaderColumn from '../index-header-column'; - -describe('index-header-column [Component]', function () { - let component; - let sortSpy; - describe('not active', function () { - beforeEach(function () { - sortSpy = sinon.spy(); - component = mount( - - ); - }); - afterEach(function () { - component = null; - sortSpy = null; - }); - it('renders the correct root classname', function () { - expect(component.find('[data-test-id="testid"]')).to.be.present(); - }); - }); - describe('active', function () { - beforeEach(function () { - sortSpy = sinon.spy(); - component = mount( - - ); - }); - afterEach(function () { - component = null; - sortSpy = null; - }); - it('renders the correct root classname', function () { - expect(component.find('[data-test-id="testid"]')).to.be.present(); - }); - }); -}); diff --git a/packages/compass-indexes/src/components/index-header-column/index.js b/packages/compass-indexes/src/components/index-header-column/index.js deleted file mode 100644 index 0c720ada8c1..00000000000 --- a/packages/compass-indexes/src/components/index-header-column/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import IndexHeaderColumn from './index-header-column'; -export default IndexHeaderColumn; diff --git a/packages/compass-indexes/src/components/index-header/index-header.jsx b/packages/compass-indexes/src/components/index-header/index-header.jsx deleted file mode 100644 index 7e75b444371..00000000000 --- a/packages/compass-indexes/src/components/index-header/index-header.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { css } from '@mongodb-js/compass-components'; -import IndexHeaderColumn from '../index-header-column'; - -const containerStyles = css({ - textAlign: 'left', -}); - -/** - * Component for the index header. - */ -class IndexHeader extends PureComponent { - static displayName = 'IndexHeader'; - static propTypes = { - isWritable: PropTypes.bool.isRequired, - isReadonly: PropTypes.bool.isRequired, - indexes: PropTypes.array.isRequired, - sortOrder: PropTypes.string.isRequired, - sortColumn: PropTypes.string.isRequired, - sortIndexes: PropTypes.func.isRequired, - }; - - /** - * Render the index header. - * - * @returns {React.Component} The index header. - */ - render() { - return ( - - - - - - - - {!this.props.isReadonly && this.props.isWritable ? : null} - - - ); - } -} - -export default IndexHeader; diff --git a/packages/compass-indexes/src/components/index-header/index-header.spec.jsx b/packages/compass-indexes/src/components/index-header/index-header.spec.jsx deleted file mode 100644 index e39f59987d3..00000000000 --- a/packages/compass-indexes/src/components/index-header/index-header.spec.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { expect } from 'chai'; - -import IndexHeader from '../index-header'; - -describe('index-header [Component]', function () { - let component; - describe('isReadable', function () { - beforeEach(function () { - component = mount( - {}} - /> - ); - }); - afterEach(function () { - component = null; - }); - - it('renders name column header', function () { - expect( - component.find('[data-test-id="index-header-name"]') - ).to.be.present(); - }); - it('renders type column header', function () { - expect( - component.find('[data-test-id="index-header-type"]') - ).to.be.present(); - }); - it('renders size column header', function () { - expect( - component.find('[data-test-id="index-header-size"]') - ).to.be.present(); - }); - it('renders usage column header', function () { - expect( - component.find('[data-test-id="index-header-usage"]') - ).to.be.present(); - }); - it('renders properties column header', function () { - expect( - component.find('[data-test-id="index-header-properties"]') - ).to.be.present(); - }); - }); - describe('isReadonly', function () { - beforeEach(function () { - component = mount( - {}} - /> - ); - }); - afterEach(function () { - component = null; - }); - - it('renders name column header', function () { - expect( - component.find('[data-test-id="index-header-name"]') - ).to.be.present(); - }); - it('renders type column header', function () { - expect( - component.find('[data-test-id="index-header-type"]') - ).to.be.present(); - }); - it('renders size column header', function () { - expect( - component.find('[data-test-id="index-header-size"]') - ).to.be.present(); - }); - it('renders usage column header', function () { - expect( - component.find('[data-test-id="index-header-usage"]') - ).to.be.present(); - }); - it('renders properties column header', function () { - expect( - component.find('[data-test-id="index-header-properties"]') - ).to.be.present(); - }); - }); -}); diff --git a/packages/compass-indexes/src/components/index-header/index.js b/packages/compass-indexes/src/components/index-header/index.js deleted file mode 100644 index e20ae7ac2a0..00000000000 --- a/packages/compass-indexes/src/components/index-header/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import IndexHeader from './index-header'; -export default IndexHeader; diff --git a/packages/compass-indexes/src/components/index-list/index-list.jsx b/packages/compass-indexes/src/components/index-list/index-list.jsx deleted file mode 100644 index b3e6943a8d6..00000000000 --- a/packages/compass-indexes/src/components/index-list/index-list.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import map from 'lodash.map'; -import IndexComponent from '../index-component'; - -/** - * Component for the index list. - */ -class IndexList extends PureComponent { - static displayName = 'IndexList'; - - static propTypes = { - indexes: PropTypes.array.isRequired, - isReadonly: PropTypes.bool.isRequired, - isWritable: PropTypes.bool.isRequired, - localAppRegistry: PropTypes.object.isRequired, - nameChanged: PropTypes.func.isRequired, - openLink: PropTypes.func.isRequired, - }; - - /** - * Render the index list. - * - * @returns {React.Component} The index list. - */ - render() { - const indexes = map(this.props.indexes, (model) => { - return ( - - ); - }); - return {indexes}; - } -} - -export default IndexList; diff --git a/packages/compass-indexes/src/components/index-list/index-list.spec.jsx b/packages/compass-indexes/src/components/index-list/index-list.spec.jsx deleted file mode 100644 index 90bff74b5f9..00000000000 --- a/packages/compass-indexes/src/components/index-list/index-list.spec.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { expect } from 'chai'; -import AppRegistry from 'hadron-app-registry'; - -import IndexList from '../index-list'; -import IndexComponent from '../index-component'; - -const indexes = [ - { - name: 'a', - type: 'regular', - fields: { serialize: () => [] }, - size: 10, - relativeSize: 10, - properties: [], - }, - { - name: 'b', - type: 'regular', - fields: { serialize: () => [] }, - size: 20, - relativeSize: 20, - properties: [], - }, -]; - -describe('index-list [Component]', function () { - const localAppRegistry = new AppRegistry(); - let component; - - describe('render', function () { - beforeEach(function () { - component = mount( - {}} - nameChanged={() => {}} - indexes={indexes} - openLink={() => {}} - /> - ); - }); - afterEach(function () { - component = null; - }); - it('renders the list', function () { - expect(component.find(IndexComponent)).to.have.lengthOf(2); - }); - }); -}); diff --git a/packages/compass-indexes/src/components/index-list/index.js b/packages/compass-indexes/src/components/index-list/index.js deleted file mode 100644 index 00e0f1e12d3..00000000000 --- a/packages/compass-indexes/src/components/index-list/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import IndexList from './index-list'; -export default IndexList; diff --git a/packages/compass-indexes/src/components/indexes-table/badge-with-icon-link.spec.tsx b/packages/compass-indexes/src/components/indexes-table/badge-with-icon-link.spec.tsx new file mode 100644 index 00000000000..2f5587767c7 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/badge-with-icon-link.spec.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { cleanup, render, screen, within } from '@testing-library/react'; +import { expect } from 'chai'; + +import BadgeWithIconLink from './badge-with-icon-link'; + +describe('BadgeWithIconLink Component', function () { + before(cleanup); + afterEach(cleanup); + it('render a badge with icon', function () { + render(); + const container = screen.getByTestId('mongodb-badge'); + expect(within(container).getByText(/mongodb/i)).to.exist; + const infoIcon = within(container).getByRole('img', { + name: /info with circle icon/i, + }); + expect(infoIcon).to.exist; + expect(infoIcon.closest('a')?.href).to.equal('https://mongodb.com/'); + }); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/badge-with-icon-link.tsx b/packages/compass-indexes/src/components/indexes-table/badge-with-icon-link.tsx new file mode 100644 index 00000000000..7a59e8674a0 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/badge-with-icon-link.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { + css, + Badge, + BadgeVariant, + Icon, + Link, + uiColors, + spacing, + focusRingStyles, + focusRingVisibleStyles, +} from '@mongodb-js/compass-components'; + +const badgeStyles = css({ + gap: spacing[2], +}); + +const linkStyles = css( + { + lineHeight: 0, + color: uiColors.white, + span: { + // LG uses backgroundImage instead of textDecoration + backgroundImage: 'none !important', + }, + '&:focus': focusRingVisibleStyles, + }, + focusRingStyles +); + +type BadgeWithIconLinkProps = { + text: string; + link: string; +}; + +const BadgeWithIconLink: React.FunctionComponent = ({ + text, + link, +}) => { + return ( + + {text} + + + + + ); +}; + +export default BadgeWithIconLink; diff --git a/packages/compass-indexes/src/components/indexes-table/drop-field.spec.tsx b/packages/compass-indexes/src/components/indexes-table/drop-field.spec.tsx new file mode 100644 index 00000000000..d1353a807f4 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/drop-field.spec.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import userEvent from '@testing-library/user-event'; + +import DropField from './drop-field'; + +describe('DropField Component', function () { + before(cleanup); + afterEach(cleanup); + it('renders delete button', function () { + const onDeleteSpy = spy(); + render(); + const button = screen.getByTestId('drop-index-button'); + expect(button).to.exist; + expect(button.getAttribute('aria-label')).to.equal( + 'Drop Index artist_id_index' + ); + expect(onDeleteSpy.callCount).to.equal(0); + userEvent.click(button); + expect(onDeleteSpy.callCount).to.equal(1); + }); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/drop-field.tsx b/packages/compass-indexes/src/components/indexes-table/drop-field.tsx new file mode 100644 index 00000000000..f6ee3a42d00 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/drop-field.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { IconButton, Icon } from '@mongodb-js/compass-components'; + +type DropFieldProps = { + name: string; + onDelete: () => void; +}; + +const DropField: React.FunctionComponent = ({ + name, + onDelete, +}) => { + return ( + + + + ); +}; + +export default DropField; diff --git a/packages/compass-indexes/src/components/indexes-table/indexes-table.spec.tsx b/packages/compass-indexes/src/components/indexes-table/indexes-table.spec.tsx new file mode 100644 index 00000000000..b569c9af300 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/indexes-table.spec.tsx @@ -0,0 +1,202 @@ +import React from 'react'; +import { cleanup, render, screen, within } from '@testing-library/react'; +import { expect } from 'chai'; +import userEvent from '@testing-library/user-event'; +import { spy } from 'sinon'; + +import { IndexesTable } from './indexes-table'; +import type { IndexModel } from './indexes-table'; + +const indexes: IndexModel[] = [ + { + cardinality: 'single', + name: '_id_', + size: 12, + relativeSize: 20, + type: 'hashed', + extra: {}, + properties: ['unique'], + fields: { + serialize() { + return [ + { + field: '_id', + value: 1, + }, + ]; + }, + }, + }, + { + cardinality: 'compound', + name: 'album_id_artist_id', + size: 20, + relativeSize: 25, + type: 'text', + extra: {}, + properties: [], + fields: { + serialize() { + return [ + { + field: 'album_id', + value: 1, + }, + { + field: 'artist_id', + value: -1, + }, + ]; + }, + }, + }, + { + cardinality: 'compound', + name: 'partial_with_ttl', + size: 20, + relativeSize: 25, + type: 'text', + extra: { + expireAfterSeconds: 3600, + partialFilterExpression: { + play_count: 30, + }, + }, + properties: ['ttl', 'partial'], + fields: { + serialize() { + return [ + { + field: 'views', + value: 1, + }, + ]; + }, + }, + }, + { + cardinality: 'single', + name: 'wildcard_index', + size: 20, + relativeSize: 25, + type: 'wildcard', + extra: { + wildcardProjection: { + fieldA: true, + _id: false, + }, + }, + properties: [], + fields: { + serialize() { + return [ + { + field: '$**', + value: 1, + }, + ]; + }, + }, + }, +]; + +const renderIndexList = ( + props: Partial> = {} +) => { + render( + {}} + onDeleteIndex={() => {}} + {...props} + /> + ); +}; + +describe('IndexesTable Component', function () { + before(cleanup); + afterEach(cleanup); + + it('renders indexes list', function () { + renderIndexList({ canDeleteIndex: true, indexes: indexes }); + + const indexesList = screen.getByTestId('indexes-list'); + expect(indexesList).to.exist; + + // Renders indexes list (table rows) + indexes.forEach((index) => { + const indexRow = screen.getByTestId(`index-row-${index.name}`); + expect(indexRow, 'it renders each index in a row').to.exist; + + // Renders index fields (table cells) + [ + 'index-name-field', + 'index-type-field', + 'index-size-field', + 'index-usage-field', + 'index-property-field', + 'index-drop-field', + ].forEach((indexCell) => { + // For _id index we always hide drop index field + if (index.name !== '_id_' && indexCell !== 'index-drop-field') { + expect(within(indexRow).getByTestId(indexCell)).to.exist; + } else { + expect(() => { + within(indexRow).getByTestId(indexCell); + }).to.throw; + } + }); + }); + }); + + it('does not render delete button when a user can not delete indexes', function () { + renderIndexList({ canDeleteIndex: false, indexes: indexes }); + const indexesList = screen.getByTestId('indexes-list'); + expect(indexesList).to.exist; + indexes.forEach((index) => { + const indexRow = screen.getByTestId(`index-row-${index.name}`); + expect(() => { + within(indexRow).getByTestId('index-drop-field'); + }).to.throw; + }); + }); + + ['Name and Definition', 'Type', 'Size', 'Usage', 'Properties'].forEach( + (column) => { + it(`sorts table by ${column}`, function () { + const onSortTableSpy = spy(); + renderIndexList({ + canDeleteIndex: true, + indexes: indexes, + onSortTable: onSortTableSpy, + }); + + const indexesList = screen.getByTestId('indexes-list'); + + const columnheader = within(indexesList).getByTestId( + `index-header-${column}` + ); + const sortButton = within(columnheader).getByRole('button', { + name: /sort/i, + }); + + expect(onSortTableSpy.callCount).to.equal(0); + + userEvent.click(sortButton); + expect(onSortTableSpy.callCount).to.equal(1); + expect(onSortTableSpy.getCalls()[0].args).to.deep.equal([ + column, + 'desc', + ]); + + userEvent.click(sortButton); + expect(onSortTableSpy.callCount).to.equal(2); + expect(onSortTableSpy.getCalls()[1].args).to.deep.equal([ + column, + 'asc', + ]); + }); + } + ); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/indexes-table.tsx b/packages/compass-indexes/src/components/indexes-table/indexes-table.tsx new file mode 100644 index 00000000000..58107d05988 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/indexes-table.tsx @@ -0,0 +1,162 @@ +import React, { useMemo } from 'react'; +import { + css, + Table, + TableHeader, + Row, + Cell, + cx, + spacing, +} from '@mongodb-js/compass-components'; + +import NameField from './name-field'; +import TypeField from './type-field'; +import SizeField from './size-field'; +import UsageField from './usage-field'; +import PropertyField from './property-field'; +import DropField from './drop-field'; + +// When row is hovered, we show the delete button +const rowStyles = css({ + ':hover': { + '.delete-cell': { + button: { + opacity: 1, + }, + }, + }, +}); +// When row is not hovered, we hide the delete button +const deleteFieldStyles = css({ + button: { + opacity: 0, + '&:focus': { + opacity: 1, + }, + }, +}); + +const tableHeaderStyles = css({ + borderWidth: 0, + borderBottomWidth: 3, + '> div': { + justifyContent: 'space-between', + }, +}); + +const cellStyles = css({ + verticalAlign: 'top', +}); + +const nameFieldStyles = css({ + paddingTop: spacing[2], + paddingBottom: spacing[2], +}); + +// todo: move to redux store when converting that to ts +export type IndexModel = { + name: string; + fields: { + serialize: () => { field: string; value: number | string }[]; + }; + type: 'geo' | 'hashed' | 'text' | 'wildcard' | 'clustered' | 'columnstore'; + cardinality: 'single' | 'compound'; + properties: ('unique' | 'sparse' | 'partial' | 'ttl' | 'collation')[]; + extra: Record>; + size: number; + relativeSize: number; + usageCount?: number; + usageSince?: Date; +}; + +type IndexesTableProps = { + darkMode?: boolean; + indexes: IndexModel[]; + canDeleteIndex: boolean; + onSortTable: (name: string, direction: 'asc' | 'desc') => void; + onDeleteIndex: (name: string) => void; +}; + +export const IndexesTable: React.FunctionComponent = ({ + indexes, + canDeleteIndex, + onSortTable, + onDeleteIndex, +}) => { + const columns = useMemo(() => { + const _columns = [ + 'Name and Definition', + 'Type', + 'Size', + 'Usage', + 'Properties', + ].map((name) => { + return ( + { + onSortTable(name, direction); + }} + /> + ); + }); + // The delete column + if (canDeleteIndex) { + _columns.push(); + } + return _columns; + }, [canDeleteIndex, onSortTable]); + + return ( + + {({ datum: index }) => ( + + +
+ +
+
+ + + + + + + + + + + + + {/* Delete column is conditional */} + {index.name !== '_id_' && canDeleteIndex && ( + +
+ onDeleteIndex(index.name)} + /> +
+
+ )} +
+ )} +
+ ); +}; diff --git a/packages/compass-indexes/src/components/indexes-table/name-field.spec.tsx b/packages/compass-indexes/src/components/indexes-table/name-field.spec.tsx new file mode 100644 index 00000000000..a0a243e08bf --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/name-field.spec.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { cleanup, render, screen, within } from '@testing-library/react'; +import { expect } from 'chai'; +import userEvent from '@testing-library/user-event'; + +import NameField from './name-field'; + +describe('NameField Component', function () { + before(cleanup); + afterEach(cleanup); + it('renders name with keys', function () { + const name = 'album_artist_title_index'; + const keys = [ + { + field: 'album_id', + value: 1, + }, + { + field: 'artist_id', + value: -1, + }, + { + field: 'title', + value: 'text', + }, + ]; + render(); + + const accordianButton = screen.getByRole('button', { + name: `Show/Hide index ${name} keys`, + }); + + expect(accordianButton).to.exist; + userEvent.click(accordianButton); + + const keysList = screen.getByRole('list'); + + const albumBadge = within(keysList).getByTestId('album_id-key'); + expect(albumBadge).to.exist; + expect( + within(albumBadge).getByRole('img', { + name: /ascending index/i, + }) + ).to.exist; + expect(albumBadge.textContent).to.equal('album_id'); + + const artistBadge = within(keysList).getByTestId('artist_id-key'); + expect(artistBadge).to.exist; + expect( + within(artistBadge).getByRole('img', { + name: /descending index/i, + }) + ).to.exist; + expect(artistBadge.textContent).to.equal('artist_id'); + + const titleBadge = within(keysList).getByTestId('title-key'); + expect(titleBadge).to.exist; + expect(titleBadge.textContent).to.equal('title(text)'); + }); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/name-field.tsx b/packages/compass-indexes/src/components/indexes-table/name-field.tsx new file mode 100644 index 00000000000..271bb5556e5 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/name-field.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { + spacing, + css, + Accordion, + Badge, + BadgeVariant, + IndexIcon, +} from '@mongodb-js/compass-components'; + +import type { IndexModel } from './indexes-table'; + +const keyListStyles = css({ + marginTop: spacing[1], + marginBottom: spacing[1], +}); + +const keyItemStyles = css({ + paddingTop: spacing[1], + paddingLeft: spacing[4], +}); + +const badgeStyles = css({ + gap: spacing[1], +}); + +type NameFieldProps = { + name: string; + keys: ReturnType; +}; + +const NameField: React.FunctionComponent = ({ name, keys }) => { + return ( + +
    + {keys.map(({ field, value }) => ( +
  • + + {field} + + +
  • + ))} +
+
+ ); +}; + +export default NameField; diff --git a/packages/compass-indexes/src/components/indexes-table/property-field.spec.tsx b/packages/compass-indexes/src/components/indexes-table/property-field.spec.tsx new file mode 100644 index 00000000000..6eb9b4c7481 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/property-field.spec.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { cleanup, render, screen, within } from '@testing-library/react'; +import { expect } from 'chai'; + +import PropertyField, { getPropertyTooltip } from './property-field'; +import getIndexHelpLink from '../../utils/index-link-helper'; + +describe('PropertyField', function () { + before(cleanup); + afterEach(cleanup); + describe('PropertyField Component', function () { + it('renders index properties', function () { + render( + + ); + + ['ttl', 'partial'].forEach((type) => { + const badge = screen.getByTestId(`${type}-badge`); + expect(badge).to.exist; + expect(badge.textContent).to.equal(type); + const infoIcon = within(badge).getByRole('img', { + name: /info with circle icon/i, + }); + expect(infoIcon).to.exist; + expect(infoIcon.closest('a')?.href).to.equal( + getIndexHelpLink(type.toUpperCase() as any) + ); + }); + }); + + it('does not render cardinality badge when its single', function () { + render( + + ); + expect(() => { + screen.getByTestId('compound-badge'); + }).to.throw; + }); + + it('renders cardinality badge when its compound', function () { + render( + + ); + const badge = screen.getByTestId('compound-badge'); + expect(badge).to.exist; + expect(badge.textContent).to.equal('compound'); + const infoIcon = within(badge).getByRole('img', { + name: /info with circle icon/i, + }); + expect(infoIcon).to.exist; + expect(infoIcon.closest('a')?.href).to.equal( + getIndexHelpLink('COMPOUND') + ); + }); + }); + + describe('getPropertyTooltip', function () { + it('returns ttl tooltip', function () { + expect( + getPropertyTooltip('ttl', { + expireAfterSeconds: 200, + }) + ).to.equal('expireAfterSeconds: 200'); + }); + + it('returns partial tooltip', function () { + expect( + getPropertyTooltip('partial', { + partialFilterExpression: { _id: true }, + }) + ).to.equal(`partialFilterExpression: ${JSON.stringify({ _id: true })}`); + }); + + it('returns null for unsupported properties', function () { + ['unique', 'sparse', 'collation'].forEach( + (prop) => expect(getPropertyTooltip(prop as any, {})).to.be.null + ); + }); + }); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/property-field.tsx b/packages/compass-indexes/src/components/indexes-table/property-field.tsx new file mode 100644 index 00000000000..49bf53dc9af --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/property-field.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import getIndexHelpLink from '../../utils/index-link-helper'; + +import { spacing, css, Tooltip, Body } from '@mongodb-js/compass-components'; +import type { IndexModel } from './indexes-table'; +import BadgeWithIconLink from './badge-with-icon-link'; + +const containerStyles = css({ + display: 'flex', + gap: spacing[1], +}); + +const partialTooltip = (partialFilterExpression: JSON) => { + return `partialFilterExpression: ${JSON.stringify(partialFilterExpression)}`; +}; + +const ttlTooltip = (expireAfterSeconds: number) => { + return `expireAfterSeconds: ${expireAfterSeconds}`; +}; + +export const getPropertyTooltip = ( + property: IndexModel['properties'][0], + extra: IndexModel['extra'] +): string | null => { + return property === 'ttl' + ? ttlTooltip(extra.expireAfterSeconds as number) + : property === 'partial' + ? partialTooltip(extra.partialFilterExpression as JSON) + : null; +}; + +const PropertyBadgeWithTooltip: React.FunctionComponent<{ + text: string; + link: string; + tooltip?: string | null; +}> = ({ text, link, tooltip }) => { + return ( + ( + + {children} + + + )} + > + {tooltip} + + ); +}; + +type PropertyFieldProps = { + extra: IndexModel['extra']; + properties: IndexModel['properties']; + cardinality: IndexModel['cardinality']; +}; + +const PropertyField: React.FunctionComponent = ({ + extra, + properties, + cardinality, +}) => { + return ( +
+ {properties.map((property) => { + return ( + + ); + })} + {cardinality === 'compound' && ( + + )} +
+ ); +}; + +export default PropertyField; diff --git a/packages/compass-indexes/src/components/indexes-table/size-field.spec.tsx b/packages/compass-indexes/src/components/indexes-table/size-field.spec.tsx new file mode 100644 index 00000000000..61b993c127c --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/size-field.spec.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import { expect } from 'chai'; + +import SizeField, { formatSize, getSizeTooltip } from './size-field'; + +describe('SizeField', function () { + before(cleanup); + afterEach(cleanup); + describe('SizeField Component', function () { + it('renders size', function () { + render(); + expect(screen.getByText(/20 b/i)).to.exist; + }); + }); + + describe('SizeField functions', function () { + it('formats size', function () { + expect(formatSize(908)).to.equal('908 B'); + expect(formatSize(2020)).to.equal('2.0 KB'); + expect(formatSize(202020)).to.equal('202.0 KB'); + }); + + it('returns correct tooltip', function () { + expect(getSizeTooltip(20)).to.equal('20.00% compared to largest index'); + expect(getSizeTooltip(8)).to.equal('8.00% compared to largest index'); + }); + }); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/size-field.tsx b/packages/compass-indexes/src/components/indexes-table/size-field.tsx new file mode 100644 index 00000000000..ab9d39984b1 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/size-field.tsx @@ -0,0 +1,37 @@ +import numeral from 'numeral'; +import React from 'react'; +import { Body, Tooltip } from '@mongodb-js/compass-components'; + +type SizeFieldProps = { + size: number; + relativeSize: number; +}; + +export const formatSize = (size: number) => { + const precision = size <= 1000 ? '0' : '0.0'; + return numeral(size).format(precision + ' b'); +}; + +export const getSizeTooltip = (relativeSize: number): string => { + return `${relativeSize.toFixed(2)}% compared to largest index`; +}; + +const SizeField: React.FunctionComponent = ({ + relativeSize, + size, +}) => { + return ( + ( + + {children} + {formatSize(size)} + + )} + > + {getSizeTooltip(relativeSize)} + + ); +}; + +export default SizeField; diff --git a/packages/compass-indexes/src/components/indexes-table/type-field.spec.tsx b/packages/compass-indexes/src/components/indexes-table/type-field.spec.tsx new file mode 100644 index 00000000000..53d4267bf77 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/type-field.spec.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { cleanup, render, screen, within } from '@testing-library/react'; +import { expect } from 'chai'; + +import TypeField, { IndexTypeTooltip, canRenderTooltip } from './type-field'; +import getIndexHelpLink from '../../utils/index-link-helper'; + +describe('TypeField', function () { + before(cleanup); + afterEach(cleanup); + describe('TypeField Component', function () { + it('renders index type', function () { + render(); + + const badge = screen.getByTestId('text-badge'); + expect(badge).to.exist; + + expect(badge.textContent).to.equal('text'); + const infoIcon = within(badge).getByRole('img', { + name: /info with circle icon/i, + }); + expect(infoIcon).to.exist; + expect(infoIcon.closest('a')?.href).to.equal(getIndexHelpLink('TEXT')); + }); + + it('renders index type - with extra information', function () { + render( + + ); + + const badge = screen.getByTestId('hashed-badge'); + expect(badge).to.exist; + }); + }); + + describe('IndexTypeTooltip Component', function () { + it('renders allowed props in tooltip', function () { + const extras: any = { + weights: 20, + default_language: 'de', + language_override: 'en', + wildcardProjection: { _id: true }, + columnstoreProjection: { name: false }, + }; + render(); + for (const key in extras) { + expect( + screen.getByText(`${key}: ${JSON.stringify(extras[key])}`), + `it renders ${key} prop in tooltip` + ).to.exist; + } + }); + + it('does not render disallowed props in tooltip', function () { + const extras: any = { + expireAfterSeconds: 200, + partialFilterExpression: { _id: true }, + }; + render(); + for (const key in extras) { + expect( + () => screen.getByText(`${key}: ${JSON.stringify(extras[key])}`), + `it does not render ${key} prop in tooltip` + ).to.throw; + } + }); + }); + + describe('canRenderTooltip function', function () { + it('renders tooltip', function () { + ['text', 'wildcard', 'columnstore'].forEach( + (x) => + expect( + canRenderTooltip(x as any), + `it renders tooltip when type is ${x}` + ).to.be.true + ); + }); + it('does not render tooltip', function () { + ['geo', 'hashed', 'clustered'].forEach( + (x) => + expect( + canRenderTooltip(x as any), + `it does not render tooltip when type is ${x}` + ).to.be.false + ); + }); + }); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/type-field.tsx b/packages/compass-indexes/src/components/indexes-table/type-field.tsx new file mode 100644 index 00000000000..d0a6381bc30 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/type-field.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import getIndexHelpLink from '../../utils/index-link-helper'; +import { Tooltip, Body } from '@mongodb-js/compass-components'; + +import type { IndexModel } from './indexes-table'; +import BadgeWithIconLink from './badge-with-icon-link'; + +export const canRenderTooltip = (type: IndexModel['type']) => { + return ['text', 'wildcard', 'columnstore'].indexOf(type) !== -1; +}; + +type TypeFieldProps = { + type: IndexModel['type']; + extra: IndexModel['extra']; +}; + +export const IndexTypeTooltip: React.FunctionComponent<{ + extra: IndexModel['extra']; +}> = ({ extra }) => { + const allowedProps = [ + 'weights', + 'default_language', + 'language_override', + 'wildcardProjection', + 'columnstoreProjection', + ]; + const items: JSX.Element[] = []; + for (const k in extra) { + if (allowedProps.includes(k)) { + items.push({`${k}: ${JSON.stringify(extra[k])}`}); + } + } + return <>{items}; +}; + +const TypeField: React.FunctionComponent = ({ + type, + extra, +}) => { + const link = getIndexHelpLink(type.toUpperCase() as any); + return ( + ( + + {children} + + + )} + > + + + ); +}; + +export default TypeField; diff --git a/packages/compass-indexes/src/components/indexes-table/usage-field.spec.tsx b/packages/compass-indexes/src/components/indexes-table/usage-field.spec.tsx new file mode 100644 index 00000000000..76a89f3924f --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/usage-field.spec.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { cleanup, render, screen } from '@testing-library/react'; +import { expect } from 'chai'; + +import UsageField, { getUsageTooltip } from './usage-field'; + +describe('UsageField', function () { + before(cleanup); + afterEach(cleanup); + + describe('UsageField Component', function () { + it('renders usage', function () { + const since = new Date(); + render(); + + const renderedText = `20 (since ${since.toDateString()})`; + expect(screen.getByText(renderedText)).to.exist; + }); + + it('renders zero when usage is not defined', function () { + const since = new Date(); + render(); + + const renderedText = `0 (since ${since.toDateString()})`; + expect(screen.getByText(renderedText)).to.exist; + }); + + it('renders N/A when since is not defined', function () { + render(); + const renderedText = '30 (N/A)'; + expect(screen.getByText(renderedText)).to.exist; + }); + }); + + describe('getUsageTooltip', function () { + it('returns correct tooltip', function () { + expect(getUsageTooltip()).to.equal( + 'Either the server does not support the $indexStats command' + + 'or the user is not authorized to execute it.' + ); + expect(getUsageTooltip(30)).to.equal( + '30 index hits since index creation or last server restart' + ); + }); + }); +}); diff --git a/packages/compass-indexes/src/components/indexes-table/usage-field.tsx b/packages/compass-indexes/src/components/indexes-table/usage-field.tsx new file mode 100644 index 00000000000..86d1253135b --- /dev/null +++ b/packages/compass-indexes/src/components/indexes-table/usage-field.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Tooltip, Body } from '@mongodb-js/compass-components'; + +const NO_USAGE_STATS = + 'Either the server does not support the $indexStats command' + + 'or the user is not authorized to execute it.'; + +export const getUsageTooltip = (usage?: number): string => { + return !usage + ? NO_USAGE_STATS + : `${usage} index hits since index creation or last server restart`; +}; + +type UsageFieldProps = { + usage?: number; + since?: Date; +}; + +const nbsp = '\u00a0'; +const UsageField: React.FunctionComponent = ({ + usage, + since, +}) => { + return ( + ( + + {children} + + {usage || 0} + {nbsp}(<>{since ? `since ${since.toDateString()}` : 'N/A'}) + + + )} + > + {getUsageTooltip(usage)} + + ); +}; + +export default UsageField; diff --git a/packages/compass-indexes/src/components/indexes-toolbar.spec.tsx b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx similarity index 100% rename from packages/compass-indexes/src/components/indexes-toolbar.spec.tsx rename to packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx diff --git a/packages/compass-indexes/src/components/indexes-toolbar.tsx b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx similarity index 51% rename from packages/compass-indexes/src/components/indexes-toolbar.tsx rename to packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx index 86a6eb3f25b..3c10a168f73 100644 --- a/packages/compass-indexes/src/components/indexes-toolbar.tsx +++ b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx @@ -12,7 +12,15 @@ import { import type AppRegistry from 'hadron-app-registry'; const toolbarStyles = css({ - padding: spacing[3], + padding: spacing[2], + backgroundColor: 'transparent', +}); + +const toolbarButtonsContainer = css({ + display: 'flex', + flexDirection: 'row', + gap: spacing[2], + justifyContent: 'flex-end', }); const createIndexButtonContainerStyles = css({ @@ -44,39 +52,39 @@ export const IndexesToolbar: React.FunctionComponent = ({ const showCreateIndexButton = !isReadonly && !isReadonlyView && !errorMessage; return ( - - {showCreateIndexButton ? ( - ( -
- - {children} -
- )} - > - {writeStateDescription} -
- ) : ( -
- )} + + {children} +
+ )} + > + {writeStateDescription} + + )} + {isReadonlyView ? ( - - - -
- - ); - } - - /** - * Render the indexes. - * - * @returns {React.Component} The indexes. - */ - render() { - return ( -
- - {!this.props.isReadonlyView && - !this.props.errorMessage && - this.renderComponent()} -
- ); - } -} - -/** - * Map the store state to properties to pass to the components. - * - * @param {Object} state - The store state. - * - * @returns {Object} The mapped properties. - */ -const mapStateToProps = (state) => ({ - indexes: state.indexes, - isWritable: state.isWritable, - isReadonly: state.isReadonly, - isReadonlyView: state.isReadonlyView, - writeStateDescription: state.description, - errorMessage: state.error, - dataService: state.dataService, - sortColumn: state.sortColumn, - sortOrder: state.sortOrder, - localAppRegistry: state.appRegistry.localAppRegistry, -}); - -/** - * Connect the redux store to the component. - * (dispatch) - */ -const MappedIndexes = connect(mapStateToProps, { - writeStateChanged, - dataServiceConnected, - sortIndexes, - reset, - nameChanged, - openLink, -})(Indexes); - -export default MappedIndexes; -export { Indexes }; diff --git a/packages/compass-indexes/src/components/indexes/indexes.spec.jsx b/packages/compass-indexes/src/components/indexes/indexes.spec.jsx deleted file mode 100644 index 93214694062..00000000000 --- a/packages/compass-indexes/src/components/indexes/indexes.spec.jsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { expect } from 'chai'; -import sinon from 'sinon'; -import AppRegistry from 'hadron-app-registry'; -import hadronApp from 'hadron-app'; -import { ErrorSummary, WarningSummary } from '@mongodb-js/compass-components'; - -import { Indexes } from '../indexes'; -import IndexHeader from '../index-header'; -import IndexList from '../index-list'; - -describe('indexes [Component]', function () { - const localAppRegistry = new AppRegistry(); - let component; - const sortIndexesSpy = sinon.spy(); - const toggleIsVisibleSpy = sinon.spy(); - const resetSpy = sinon.spy(); - const nameChangedSpy = sinon.spy(); - const openLinkSpy = sinon.spy(); - - before(function () { - global.hadronApp = hadronApp; - global.hadronApp.appRegistry = localAppRegistry; - }); - - context('when the collection is not a readonly view', function () { - beforeEach(function () { - component = mount( - - ); - }); - - afterEach(function () { - component = null; - }); - - it('renders a create-index-button', function () { - expect( - component - .find('button') - .findWhere((node) => node.text() === 'Create Index') - ).to.be.present(); - }); - - it('does not render errors or warnings', function () { - expect(component.find(ErrorSummary)).to.not.be.present(); - expect(component.find(WarningSummary)).to.not.be.present(); - }); - - it('renders the list and header', function () { - expect(component.find(IndexHeader)).to.be.present(); - expect(component.find(IndexList)).to.be.present(); - }); - }); - - context('when the collection is a readonly view', function () { - beforeEach(function () { - component = mount( - - ); - }); - - afterEach(function () { - component = null; - }); - - it('does not render a create-index-button', function () { - expect( - component - .find('button') - .findWhere((node) => node.text() === 'Create Index') - ).to.not.be.present(); - }); - - it('renders a warning summary', function () { - expect(component.find(WarningSummary)).to.be.present(); - expect(component.find(WarningSummary).text()).to.equal( - 'Readonly views may not contain indexes.' - ); - }); - - it('does not render the list or header', function () { - expect(component.find(IndexHeader)).to.not.be.present(); - expect(component.find(IndexList)).to.not.be.present(); - }); - }); - - context('when the distribution is readonly', function () { - beforeEach(function () { - component = mount( - - ); - }); - - afterEach(function () { - component = null; - }); - - it('does not render a create-index-button', function () { - expect( - component - .find('button') - .findWhere((node) => node.text() === 'Create Index') - ).to.not.be.present(); - }); - - it('does not render errors or warnings', function () { - expect(component.find(ErrorSummary)).to.not.be.present(); - expect(component.find(WarningSummary)).to.not.be.present(); - }); - - it('renders the main column', function () { - expect(component.find(IndexHeader)).to.be.present(); - expect(component.find(IndexList)).to.be.present(); - }); - }); - - context('when there is an error', function () { - beforeEach(function () { - component = mount( - - ); - }); - - afterEach(function () { - component = null; - }); - - it('renders an error summary', function () { - expect(component.find(ErrorSummary)).to.be.present(); - expect(component.find(ErrorSummary).text()).to.equal('a test error'); - }); - }); -}); diff --git a/packages/compass-indexes/src/components/indexes/indexes.spec.tsx b/packages/compass-indexes/src/components/indexes/indexes.spec.tsx new file mode 100644 index 00000000000..5c2f73e6712 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes/indexes.spec.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { cleanup, render, screen, within } from '@testing-library/react'; +import { expect } from 'chai'; +import AppRegistry from 'hadron-app-registry'; + +import { Indexes } from './indexes'; + +const renderIndexes = ( + props: Partial> = {} +) => { + const appRegistry = new AppRegistry(); + render( + {}} + {...props} + /> + ); +}; + +describe('Indexes Component', function () { + before(cleanup); + afterEach(cleanup); + + it('renders indexes card', function () { + renderIndexes(); + expect(screen.getByTestId('indexes')).to.exist; + }); + + it('renders indexes toolbar', function () { + renderIndexes(); + expect(screen.getByTestId('indexes-toolbar')).to.exist; + }); + + it('does not render indexes list when its a readonly view', function () { + renderIndexes({ + indexes: [], + isReadonlyView: true, + error: undefined, + }); + expect(() => { + screen.getByTestId('indexes-list'); + }).to.throw; + }); + it('does not render indexes list when there is an error', function () { + renderIndexes({ + indexes: [], + isReadonlyView: false, + error: 'Some random error', + }); + expect(() => { + screen.getByTestId('indexes-list'); + }).to.throw; + }); + it('renders indexes list', function () { + renderIndexes({ + indexes: [ + { + cardinality: 'single', + name: '_id_', + size: 12, + relativeSize: 20, + type: 'hashed', + extra: {}, + properties: ['unique'], + fields: { + serialize() { + return [ + { + field: '_id', + value: 1, + }, + ]; + }, + }, + }, + ], + isReadonlyView: false, + error: undefined, + }); + + const indexesList = screen.getByTestId('indexes-list'); + expect(indexesList).to.exist; + expect(within(indexesList).getByTestId('index-row-_id_')).to.exist; + }); +}); diff --git a/packages/compass-indexes/src/components/indexes/indexes.tsx b/packages/compass-indexes/src/components/indexes/indexes.tsx new file mode 100644 index 00000000000..56c2e88c653 --- /dev/null +++ b/packages/compass-indexes/src/components/indexes/indexes.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { css, Card, spacing } from '@mongodb-js/compass-components'; +import { connect } from 'react-redux'; +import type AppRegistry from 'hadron-app-registry'; + +import { sortIndexes } from '../../modules/indexes'; + +import { IndexesToolbar } from '../indexes-toolbar/indexes-toolbar'; +import { IndexesTable } from '../indexes-table/indexes-table'; +import type { IndexModel } from '../indexes-table/indexes-table'; + +const containerStyles = css({ + margin: spacing[3], + padding: spacing[3], + display: 'grid', + gridTemplateAreas: ` + 'toolbar' + 'indexTable' + `, + width: '100%', + overflow: 'hidden', + alignContent: 'start', +}); +const toolbarStyles = css({ + gridArea: 'toolbar', +}); +const indexTableStyles = css({ + gridArea: 'indexTable', + overflow: 'auto', +}); + +type IndexesProps = { + indexes: IndexModel[]; + isWritable: boolean; + isReadonly: boolean; + isReadonlyView: boolean; + description?: string; + error?: string; + localAppRegistry: AppRegistry; + onSortTable: (name: string, direction: 'asc' | 'desc') => void; +}; + +export const Indexes: React.FunctionComponent = ({ + indexes, + isWritable, + isReadonly, + isReadonlyView, + description, + error, + localAppRegistry, + onSortTable, +}) => { + const onDeleteIndex = (name: string) => { + return localAppRegistry.emit('toggle-drop-index-modal', true, name); + }; + return ( + +
+ +
+ {!isReadonlyView && !error && ( +
+ +
+ )} +
+ ); +}; + +const mapState = ({ + indexes, + isWritable, + isReadonly, + isReadonlyView, + description, + error, + appRegistry: { localAppRegistry }, +}: any) => ({ + indexes, + isWritable, + isReadonly, + isReadonlyView, + description, + error, + localAppRegistry, +}); + +const mapDispatch = { + onSortTable: sortIndexes, +}; + +export default connect(mapState, mapDispatch)(Indexes as any); diff --git a/packages/compass-indexes/src/components/name-column/index.js b/packages/compass-indexes/src/components/name-column/index.js deleted file mode 100644 index 5d2fd30b7cd..00000000000 --- a/packages/compass-indexes/src/components/name-column/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import NameColumn from './name-column'; -export default NameColumn; diff --git a/packages/compass-indexes/src/components/name-column/name-column.jsx b/packages/compass-indexes/src/components/name-column/name-column.jsx deleted file mode 100644 index cf9c7d1f7bf..00000000000 --- a/packages/compass-indexes/src/components/name-column/name-column.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { - spacing, - css, - IndexIcon, - BadgeVariant, - Badge, - Accordion, -} from '@mongodb-js/compass-components'; - -const keyListStyles = css({ - marginTop: spacing[1], - marginBottom: spacing[1], -}); - -const keyItemStyles = css({ - paddingTop: spacing[1], - paddingLeft: spacing[4], -}); - -class NameColumn extends PureComponent { - static displayName = 'NameColumn'; - - static propTypes = { - index: PropTypes.object.isRequired, - }; - - render() { - const indexName = this.props.index.name; - const indexKeys = this.props.index.fields.serialize(); - return ( - - -
    - {indexKeys.map(({ field, value }) => ( -
  • - - {field} -   - - -
  • - ))} -
-
- - ); - } -} - -export default NameColumn; diff --git a/packages/compass-indexes/src/components/property-column/index.js b/packages/compass-indexes/src/components/property-column/index.js deleted file mode 100644 index 1e3d67f9e2e..00000000000 --- a/packages/compass-indexes/src/components/property-column/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import PropertyColumn from './property-column'; -export default PropertyColumn; diff --git a/packages/compass-indexes/src/components/property-column/property-column.jsx b/packages/compass-indexes/src/components/property-column/property-column.jsx deleted file mode 100644 index a8c507e1fbe..00000000000 --- a/packages/compass-indexes/src/components/property-column/property-column.jsx +++ /dev/null @@ -1,122 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import getIndexHelpLink from '../../utils/index-link-helper'; - -import { - spacing, - css, - Tooltip, - Body, - Badge, - BadgeVariant, - Icon, - Link, - uiColors, -} from '@mongodb-js/compass-components'; - -const contentStyles = css({ - display: 'flex', - gap: spacing[1], -}); - -const badgeStyles = css({ - gap: spacing[2], -}); - -const iconLinkStyles = css({ - lineHeight: 0, - color: uiColors.white, - span: { - // LG uses backgroundImage instead of textDecoration - backgroundImage: 'none !important', - }, -}); - -class PropertyColumn extends PureComponent { - static displayName = 'PropertyColumn'; - - static propTypes = { - index: PropTypes.object.isRequired, - openLink: PropTypes.func.isRequired, - }; - - _partialTooltip() { - const { partialFilterExpression } = this.props.index.extra; - return `partialFilterExpression: ${JSON.stringify( - partialFilterExpression - )}`; - } - - _ttlTooltip() { - const { expireAfterSeconds } = this.props.index.extra; - return `expireAfterSeconds: ${expireAfterSeconds}`; - } - - renderItemWithTooltip(text, link, tooltip) { - return ( - ( - - {children} - - {text} - - - - - - )} - > - {tooltip} - - ); - } - - renderCardinality() { - const { cardinality } = this.props.index; - if (cardinality !== 'compound') { - return null; - } - return this.renderItemWithTooltip( - cardinality, - getIndexHelpLink('COMPOUND') - ); - } - - renderProperty(prop) { - const tooltip = - prop === 'ttl' - ? this._ttlTooltip() - : prop === 'partial' - ? this._partialTooltip() - : null; - - return this.renderItemWithTooltip( - prop, - getIndexHelpLink(prop.toUpperCase()), - tooltip - ); - } - - render() { - const properties = this.props.index.properties.map( - this.renderProperty.bind(this) - ); - return ( - -
- {properties} - {this.renderCardinality()} -
- - ); - } -} - -export default PropertyColumn; diff --git a/packages/compass-indexes/src/components/size-column/index.js b/packages/compass-indexes/src/components/size-column/index.js deleted file mode 100644 index 044cc84c4a4..00000000000 --- a/packages/compass-indexes/src/components/size-column/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import SizeColumn from './size-column'; -export default SizeColumn; diff --git a/packages/compass-indexes/src/components/size-column/size-column.jsx b/packages/compass-indexes/src/components/size-column/size-column.jsx deleted file mode 100644 index 9023ca4716f..00000000000 --- a/packages/compass-indexes/src/components/size-column/size-column.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import numeral from 'numeral'; -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import { Body, Tooltip } from '@mongodb-js/compass-components'; - -/** - * Component for the size column. - */ -class SizeColumn extends PureComponent { - static displayName = 'SizeColumn'; - - static propTypes = { - size: PropTypes.number.isRequired, - relativeSize: PropTypes.number.isRequired, - }; - - _format(size) { - const precision = size <= 1000 ? '0' : '0.0'; - return numeral(size).format(precision + ' b'); - } - - render() { - const indexSize = this._format(this.props.size); - const tooltip = `${this.props.relativeSize.toFixed( - 2 - )}% compared to largest index`; - return ( - - ( - - {children} - {indexSize} - - )} - > - {tooltip} - - - ); - } -} - -export default SizeColumn; diff --git a/packages/compass-indexes/src/components/type-column/index.js b/packages/compass-indexes/src/components/type-column/index.js deleted file mode 100644 index 01de3d8b138..00000000000 --- a/packages/compass-indexes/src/components/type-column/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import TypeColumn from './type-column'; -export default TypeColumn; diff --git a/packages/compass-indexes/src/components/type-column/type-column.jsx b/packages/compass-indexes/src/components/type-column/type-column.jsx deleted file mode 100644 index 88f13ae276d..00000000000 --- a/packages/compass-indexes/src/components/type-column/type-column.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import map from 'lodash.map'; -import pick from 'lodash.pick'; -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import getIndexHelpLink from '../../utils/index-link-helper'; -import { - spacing, - css, - Tooltip, - Body, - Badge, - BadgeVariant, - Icon, - Link, - uiColors, -} from '@mongodb-js/compass-components'; - -const badgeStyles = css({ - gap: spacing[2], -}); - -const iconLinkStyles = css({ - lineHeight: 0, - color: uiColors.white, - span: { - // LG uses backgroundImage instead of textDecoration - backgroundImage: 'none !important', - }, -}); - -/** - * Component for the type column. - */ -class TypeColumn extends PureComponent { - static displayName = 'TypeColumn'; - - static propTypes = { - index: PropTypes.object.isRequired, - openLink: PropTypes.func.isRequired, - }; - - canRenderTooltip() { - return ( - ['text', 'wildcard', 'columnstore'].indexOf(this.props.index.type) !== -1 - ); - } - - renderTooltip() { - const info = pick(this.props.index.extra, [ - 'weights', - 'default_language', - 'language_override', - 'wildcardProjection', - 'columnstoreProjection', - ]); - const items = map(info, (v, k) => { - return {`${k}: ${JSON.stringify(v)}`}; - }); - return <>{items}; - } - - render() { - const helpLink = getIndexHelpLink(this.props.index.type.toUpperCase()); - return ( - - ( - - {children} - - {this.props.index.type} - - - - - - )} - > - {this.renderTooltip()} - - - ); - } -} - -export default TypeColumn; diff --git a/packages/compass-indexes/src/components/usage-column/index.js b/packages/compass-indexes/src/components/usage-column/index.js deleted file mode 100644 index f9017806f5f..00000000000 --- a/packages/compass-indexes/src/components/usage-column/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import UsageColumn from './usage-column'; -export default UsageColumn; diff --git a/packages/compass-indexes/src/components/usage-column/usage-column.jsx b/packages/compass-indexes/src/components/usage-column/usage-column.jsx deleted file mode 100644 index cafab04e2d6..00000000000 --- a/packages/compass-indexes/src/components/usage-column/usage-column.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import isUndefined from 'lodash.isundefined'; -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import { Tooltip, Body } from '@mongodb-js/compass-components'; - -const NO_USAGE_STATS = - 'Either the server does not support the $indexStats command' + - 'or the user is not authorized to execute it.'; - -class UsageColumn extends PureComponent { - static displayName = 'UsageColumn'; - - static propTypes = { - usage: PropTypes.any, - since: PropTypes.any, - }; - - tooltip() { - if (isUndefined(this.props.usage)) { - return NO_USAGE_STATS; - } - return `${this.props.usage} index hits since index creation or last\n server restart`; - } - - renderSince() { - if (isUndefined(this.props.since)) { - return null; - } - return ( - - (since  - {this.props.since ? this.props.since.toDateString() : 'N/A'}) - - ); - } - - render() { - const usage = isUndefined(this.props.usage) ? '0' : this.props.usage; - const tooltip = this.tooltip(); - return ( - - ( - - {children} - - {usage} {this.renderSince()} - - - )} - > - {tooltip} - - - ); - } -} - -export default UsageColumn; diff --git a/packages/compass-indexes/src/modules/indexes.js b/packages/compass-indexes/src/modules/indexes.js index 62cd86139ec..53c6e9c5619 100644 --- a/packages/compass-indexes/src/modules/indexes.js +++ b/packages/compass-indexes/src/modules/indexes.js @@ -25,8 +25,8 @@ export const SORT_INDEXES = `${PREFIX}/indexes/SORT_INDEXES`; * Default sortOrder */ export const DEFAULT = 'Name and Definition'; -export const ASC = 'fa-sort-asc'; -export const DESC = 'fa-sort-desc'; +export const ASC = 'asc'; +export const DESC = 'desc'; export const USAGE = 'Usage'; /** @@ -171,21 +171,17 @@ export const loadIndexes = (indexes) => ({ indexes: indexes, }); -/** - * Action creator for sort indexes events. - * - * @param {Array} indexes - The raw indexes list. - * @param {String} column - The column. - * @param {String} order - The order. - * - * @returns {Object} The load indexes action. - */ -export const sortIndexes = (indexes, column, order) => ({ - type: SORT_INDEXES, - indexes: indexes, - column: column, - order: order, -}); +export const sortIndexes = (column, order) => { + return (dispatch, getState) => { + const { indexes } = getState(); + return dispatch({ + type: SORT_INDEXES, + indexes, + column, + order, + }); + }; +}; /** * Load indexes from DB. diff --git a/packages/compass-indexes/src/modules/indexes.spec.js b/packages/compass-indexes/src/modules/indexes.spec.js index 8775ee56d8b..60d6fd1baf9 100644 --- a/packages/compass-indexes/src/modules/indexes.spec.js +++ b/packages/compass-indexes/src/modules/indexes.spec.js @@ -31,17 +31,33 @@ describe('indexes module', function () { context('when the column is Usage', function () { context('when sorting asc', function () { it('returns the sorted indexes list', function () { - expect( - reducer(undefined, sortIndexes(defaultSort, USAGE, ASC)) - ).to.deep.equal(usageSort); + const dispatch = (args) => args; + const getState = () => { + return { + indexes: defaultSort, + }; + }; + const result = reducer( + undefined, + sortIndexes(USAGE, ASC)(dispatch, getState) + ); + expect(result).to.deep.equal(usageSort); }); }); context('when sorting desc', function () { it('returns the sorted indexes list', function () { - expect( - reducer(undefined, sortIndexes(defaultSort, USAGE, DESC)) - ).to.deep.equal(usageSortDesc); + const dispatch = (args) => args; + const getState = () => { + return { + indexes: defaultSort, + }; + }; + const result = reducer( + undefined, + sortIndexes(USAGE, DESC)(dispatch, getState) + ); + expect(result).to.deep.equal(usageSortDesc); }); }); }); @@ -49,17 +65,33 @@ describe('indexes module', function () { context('when the column is Name and Definition', function () { context('when sorting asc', function () { it('returns the sorted indexes list', function () { - expect( - reducer(undefined, sortIndexes(usageSort, DEFAULT, ASC)) - ).to.deep.equal(defaultSort); + const dispatch = (args) => args; + const getState = () => { + return { + indexes: usageSort, + }; + }; + const result = reducer( + undefined, + sortIndexes(DEFAULT, ASC)(dispatch, getState) + ); + expect(result).to.deep.equal(defaultSort); }); }); context('when sorting desc', function () { it('returns the sorted indexes list', function () { - expect( - reducer(undefined, sortIndexes(usageSort, DEFAULT, DESC)) - ).to.deep.equal(defaultSortDesc); + const dispatch = (args) => args; + const getState = () => { + return { + indexes: usageSort, + }; + }; + const result = reducer( + undefined, + sortIndexes(DEFAULT, DESC)(dispatch, getState) + ); + expect(result).to.deep.equal(defaultSortDesc); }); }); }); @@ -84,7 +116,11 @@ describe('indexes module', function () { describe('#sortIndexes', function () { it('returns the action', function () { - expect(sortIndexes([], 'Database Name', DESC)).to.deep.equal({ + const dispatch = (x) => x; + const getState = () => ({ indexes: [] }); + expect( + sortIndexes('Database Name', DESC)(dispatch, getState) + ).to.deep.equal({ type: SORT_INDEXES, indexes: [], column: 'Database Name', diff --git a/packages/compass-indexes/src/modules/sort-column.spec.js b/packages/compass-indexes/src/modules/sort-column.spec.js index b51d1390b31..6c5ad7e5541 100644 --- a/packages/compass-indexes/src/modules/sort-column.spec.js +++ b/packages/compass-indexes/src/modules/sort-column.spec.js @@ -7,9 +7,11 @@ describe('sort column module', function () { describe('#reducer', function () { context('when an action is provided', function () { it('returns the new column', function () { - expect(reducer(undefined, sortIndexes(null, 'Size', ''))).to.equal( - 'Size' - ); + const dispatch = (x) => x; + const getState = () => ({}); + expect( + reducer(undefined, sortIndexes('Size', '')(dispatch, getState)) + ).to.equal('Size'); }); }); diff --git a/packages/compass-indexes/src/modules/sort-order.spec.js b/packages/compass-indexes/src/modules/sort-order.spec.js index e0b4e9e6c4c..32dcec67430 100644 --- a/packages/compass-indexes/src/modules/sort-order.spec.js +++ b/packages/compass-indexes/src/modules/sort-order.spec.js @@ -7,9 +7,11 @@ describe('sort order module', function () { describe('#reducer', function () { context('when an action is provided', function () { it('returns the new order', function () { - expect(reducer(undefined, sortIndexes(null, '', 'desc'))).to.equal( - 'desc' - ); + const dispatch = (x) => x; + const getState = () => ({}); + expect( + reducer(undefined, sortIndexes('', 'desc')(dispatch, getState)) + ).to.equal('desc'); }); }); diff --git a/packages/compass-indexes/src/plugin.jsx b/packages/compass-indexes/src/plugin.jsx index 7759ba3b4b7..e3ecf97b314 100644 --- a/packages/compass-indexes/src/plugin.jsx +++ b/packages/compass-indexes/src/plugin.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Provider } from 'react-redux'; import PropTypes from 'prop-types'; -import Indexes from './components/indexes'; +import Indexes from './components/indexes/indexes'; class Plugin extends Component { static displayName = 'IndexesPlugin'; diff --git a/packages/compass-indexes/src/utils/index-link-helper.ts b/packages/compass-indexes/src/utils/index-link-helper.ts index 352833d6a05..f89aa579d2b 100644 --- a/packages/compass-indexes/src/utils/index-link-helper.ts +++ b/packages/compass-indexes/src/utils/index-link-helper.ts @@ -31,7 +31,6 @@ const HELP_URLS = { * The function looks up index help links. * * @param {String} section - The name of the section to open. - * @returns {String} - the link. */ export default function getIndexHelpLink(section: keyof typeof HELP_URLS) { return HELP_URLS[section] || null;