diff --git a/package-lock.json b/package-lock.json index aa2223f2b0c..d8c564b0eab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5675,13 +5675,13 @@ } }, "node_modules/@leafygreen-ui/popover": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/popover/-/popover-7.2.2.tgz", - "integrity": "sha512-MtrRHBh5Rw3hY7aQRzPa9209HY2kVCtMY5nOqS3R4JQDqD5VoC5HfYAiSQ7BJRgI9GFZ81sEVfPIYZsNQoqXpg==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/popover/-/popover-7.2.3.tgz", + "integrity": "sha512-PIUA3hIIVS/fp4hcmi7tpZV3KDmXm29IyZXKJsKN1duiLSO9mKaynmywG80iezPuRYKfeoCsVndnSPpoZX8Vng==", "dependencies": { "@leafygreen-ui/hooks": "^7.0.0", "@leafygreen-ui/leafygreen-provider": "^2.1.3", - "@leafygreen-ui/lib": "^9.0.0", + "@leafygreen-ui/lib": "^9.2.1", "@leafygreen-ui/portal": "^4.0.0", "lodash": "^4.17.21", "react-transition-group": "^4.4.1" @@ -62895,7 +62895,7 @@ "@leafygreen-ui/banner": "^3.0.9", "@leafygreen-ui/button": "^12.0.5", "@leafygreen-ui/card": "^5.1.4", - "@leafygreen-ui/checkbox": "^6.0.6", + "@leafygreen-ui/checkbox": "^7.0.1", "@leafygreen-ui/code": "^9.4.0", "@leafygreen-ui/confirmation-modal": "^2.2.3", "@leafygreen-ui/emotion": "^4.0.0", @@ -69987,7 +69987,6 @@ "@mongodb-js/compass-logging": "^1.0.0", "@mongodb-js/mongodb-redux-common": "^2.0.0", "bson": "^4.4.1", - "hadron-react-buttons": "^6.0.0", "hadron-react-components": "^6.0.0", "mongodb-index-model": "^4.0.0", "react": "^16.14.0" @@ -70013,13 +70012,13 @@ "lodash.map": "^4.6.0", "lodash.max": "^4.0.1", "lodash.pick": "^4.4.0", - "lodash.pluck": "^3.1.2", "mocha": "^8.4.0", + "mongodb": "^4.6.0", + "mongodb-data-service": "^22.0.0", "mongodb-query-parser": "^2.4.6", "numeral": "^2.0.6", "nyc": "^15.1.0", "prop-types": "^15.7.2", - "react-bootstrap": "^0.32.1", "react-dom": "^16.14.0", "react-redux": "^5.0.6", "redux": "^4.1.2", @@ -70034,7 +70033,6 @@ "@mongodb-js/compass-logging": "^1.0.0", "@mongodb-js/mongodb-redux-common": "^2.0.0", "bson": "^4.4.1", - "hadron-react-buttons": "^6.0.0", "hadron-react-components": "^6.0.0", "mongodb-index-model": "^4.0.0", "react": "^16.14.0" @@ -96965,13 +96963,13 @@ } }, "@leafygreen-ui/popover": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/popover/-/popover-7.2.2.tgz", - "integrity": "sha512-MtrRHBh5Rw3hY7aQRzPa9209HY2kVCtMY5nOqS3R4JQDqD5VoC5HfYAiSQ7BJRgI9GFZ81sEVfPIYZsNQoqXpg==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/popover/-/popover-7.2.3.tgz", + "integrity": "sha512-PIUA3hIIVS/fp4hcmi7tpZV3KDmXm29IyZXKJsKN1duiLSO9mKaynmywG80iezPuRYKfeoCsVndnSPpoZX8Vng==", "requires": { "@leafygreen-ui/hooks": "^7.0.0", "@leafygreen-ui/leafygreen-provider": "^2.1.3", - "@leafygreen-ui/lib": "^9.0.0", + "@leafygreen-ui/lib": "^9.2.1", "@leafygreen-ui/portal": "^4.0.0", "lodash": "^4.17.21", "react-transition-group": "^4.4.1" @@ -102662,7 +102660,7 @@ "@leafygreen-ui/banner": "^3.0.9", "@leafygreen-ui/button": "^12.0.5", "@leafygreen-ui/card": "^5.1.4", - "@leafygreen-ui/checkbox": "^6.0.6", + "@leafygreen-ui/checkbox": "^7.0.1", "@leafygreen-ui/code": "^9.4.0", "@leafygreen-ui/confirmation-modal": "^2.2.3", "@leafygreen-ui/emotion": "^4.0.0", @@ -102735,7 +102733,8 @@ }, "dependencies": { "@leafygreen-ui/checkbox": { - "version": "https://registry.npmjs.org/@leafygreen-ui/checkbox/-/checkbox-7.0.1.tgz", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/checkbox/-/checkbox-7.0.1.tgz", "integrity": "sha512-KA96oQK8pQfJn49yuisQAmxCAj95m3U+6pFO8D2+DLzhDSQtISO8TOLCeT9Bvs/tbvOUnqGAc5UjRPhrAjHO+Q==", "requires": { "@leafygreen-ui/hooks": "^7.0.0", @@ -107551,22 +107550,21 @@ "eslint": "^7.25.0", "hadron-app": "^5.0.0", "hadron-app-registry": "^9.0.0", - "hadron-react-buttons": "^6.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", - "lodash.pluck": "^3.1.2", "mocha": "^8.4.0", + "mongodb": "^4.6.0", + "mongodb-data-service": "^22.0.0", "mongodb-index-model": "^4.0.0", "mongodb-query-parser": "^2.4.6", "numeral": "^2.0.6", "nyc": "^15.1.0", "prop-types": "^15.7.2", "react": "^16.14.0", - "react-bootstrap": "^0.32.1", "react-dom": "^16.14.0", "react-redux": "^5.0.6", "redux": "^4.1.2", diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index aaedb9b2d84..c3360922e6c 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -35,7 +35,7 @@ "@leafygreen-ui/banner": "^3.0.9", "@leafygreen-ui/button": "^12.0.5", "@leafygreen-ui/card": "^5.1.4", - "@leafygreen-ui/checkbox": "^6.0.6", + "@leafygreen-ui/checkbox": "^7.0.1", "@leafygreen-ui/code": "^9.4.0", "@leafygreen-ui/confirmation-modal": "^2.2.3", "@leafygreen-ui/emotion": "^4.0.0", diff --git a/packages/compass-components/src/components/collapsible-field-set.spec.tsx b/packages/compass-components/src/components/collapsible-field-set.spec.tsx new file mode 100644 index 00000000000..ee46c836e33 --- /dev/null +++ b/packages/compass-components/src/components/collapsible-field-set.spec.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { expect } from 'chai'; +import sinon from 'sinon'; + +import { TextInput } from './leafygreen'; +import { render, screen, cleanup } from '@testing-library/react'; + +import { CollapsibleFieldSet } from './collapsible-field-set'; + +function collapsibleFieldSet(toggled: boolean) { + const onToggleSpy = sinon.spy(); + return ( + + + + ); +} + +describe('CollapsibleFieldSet Component', function () { + afterEach(cleanup); + + it('should not display a child input', function () { + render(collapsibleFieldSet(false)); + const checkbox = screen.getByTestId('my-checkbox'); + expect(checkbox).to.exist; + expect(screen.queryByTestId('my-input')).to.not.exist; + }); + + it('should display a child input', function () { + render(collapsibleFieldSet(true)); + const checkbox = screen.getByTestId('my-checkbox'); + expect(checkbox).to.exist; + expect(screen.queryByTestId('my-input')).to.exist; + }); +}); diff --git a/packages/compass-components/src/components/collapsible-field-set.tsx b/packages/compass-components/src/components/collapsible-field-set.tsx new file mode 100644 index 00000000000..dc4a15d67b8 --- /dev/null +++ b/packages/compass-components/src/components/collapsible-field-set.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { css } from '@leafygreen-ui/emotion'; +import { Link, Checkbox, Label } from './leafygreen'; +import { spacing } from '@leafygreen-ui/tokens'; +import { withTheme } from '../hooks/use-theme'; +import { Description } from '@leafygreen-ui/typography'; + +const infoLinkStyles = css({ + marginLeft: spacing[1], +}); + +const collapsibleFieldsetStyles = css({ + margin: `${spacing[3]}px 0`, + fieldset: { + paddingLeft: `${spacing[4]}px`, + }, +}); + +export type CollapsibleFieldSetProps = { + darkMode?: boolean; + dataTestId?: string; + children?: React.ReactElement; + label: string; + description?: React.ReactElement | string; + disabled?: boolean; + helpUrl?: string; + onToggle: (checked: boolean) => void; + toggled?: boolean; +}; + +const UnthemedCollapsibleFieldSet = ({ + darkMode, + description, + disabled, + helpUrl, + label, + onToggle, + toggled, + dataTestId, + children, +}: React.PropsWithChildren): React.ReactElement => { + const labelId = dataTestId || 'collapsible-fieldset-props'; + return ( +
+ { + onToggle(event.target.checked); + }} + disabled={disabled} + label={} + description={ + !description + ? '' + : (( + + {description} + {!!helpUrl && ( + + Learn More + + )} + + ) as any) // LG Checkbox expects a string description, but we use Description component to include helpUrl. + } + checked={toggled} + id={labelId} + darkMode={darkMode} + /> + {toggled &&
{children}
} +
+ ); +}; + +const CollapsibleFieldSet = withTheme(UnthemedCollapsibleFieldSet); +export { CollapsibleFieldSet }; diff --git a/packages/compass-components/src/components/leafy-green/combobox/combobox.styles.ts b/packages/compass-components/src/components/leafy-green/combobox/combobox.styles.ts index 98e63dbd536..c541732fc18 100644 --- a/packages/compass-components/src/components/leafy-green/combobox/combobox.styles.ts +++ b/packages/compass-components/src/components/leafy-green/combobox/combobox.styles.ts @@ -351,3 +351,11 @@ export const menuMessage = css` height: 1em; } `; + +// Show combobox menu under the input workaround when usePortal=false. +export const popoverStyle = () => css` + overflow: hidden; + top: auto; + left: auto; + box-shadow: 0 3px 7px 0 rgba(6, 22, 33, 0.3); +`; diff --git a/packages/compass-components/src/components/leafy-green/combobox/combobox.tsx b/packages/compass-components/src/components/leafy-green/combobox/combobox.tsx index 232d50636f3..9a6519fb2de 100644 --- a/packages/compass-components/src/components/leafy-green/combobox/combobox.tsx +++ b/packages/compass-components/src/components/leafy-green/combobox/combobox.tsx @@ -45,6 +45,7 @@ import { menuMessage, menuStyle, menuWrapperStyle, + popoverStyle, } from './combobox.styles'; import { InternalComboboxGroup } from './combobox-group'; import type { OptionObject } from './util'; @@ -886,12 +887,18 @@ export default function Combobox({ setInputFocus(cursorPos); } + + // Open menu on click instead of on focus. + // https://jira.mongodb.org/browse/PD-2321 + openMenu(); }; // Fired when the wrapper gains focus const handleInputWrapperFocus = () => { scrollToEnd(); - openMenu(); + // To prevent Combobox menu from automatically being opened on focus. + // https://jira.mongodb.org/browse/PD-2321 + // openMenu(); }; // Fired onChange @@ -1152,7 +1159,12 @@ export default function Combobox({ justify="middle" refEl={comboboxRef} adjustOnMutation={true} - className={menuWrapperStyle({ darkMode, size, width: menuWidth })} + className={cx( + popoverStyle(), + menuWrapperStyle({ darkMode, size, width: menuWidth }) + )} + usePortal={false} // combobox@0.9.0 does not have implemented functionality for usePortal yet. + popoverZIndex={999999} >
{ - return `[data-test-id="create-index-field-name-${idx}"] input`; + return `[data-testid="create-index-fields-name-${idx}"] input`; }; export const CreateIndexModalFieldTypeSelectButton = (idx: number): string => { - return `[data-test-id="create-index-field-type-${idx}"] button`; + return `[data-testid="create-index-fields-type-${idx}"] button`; }; export const CreateIndexModalFieldTypeSelectMenu = (idx: number): string => { - return `[data-test-id="create-index-field-type-${idx}"] #create-index-field-type-${idx}-menu`; + return `[data-testid="create-index-fields-type-${idx}"] #create-index-fields-type-select-${idx}-menu`; }; -export const CreateIndexConfirmButton = '[data-test-id="create-index-button"]'; + +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`; export const DropIndexModal = '[data-testid="drop_index_modal"]'; export const DropIndexModalConfirmName = 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 cdbecdb802d..0ebc8673777 100644 --- a/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts @@ -65,7 +65,6 @@ describe('Collection indexes tab', function () { Selectors.CreateIndexModalFieldTypeSelectButton(0) ); await fieldTypeSelect.waitForDisplayed(); - await fieldTypeSelect.click(); const fieldTypeSelectMenu = await browser.$( @@ -144,7 +143,9 @@ describe('Collection indexes tab', function () { const indexToggleIsWildcard = await browser.$( Selectors.IndexToggleIsWildcard ); - await indexToggleIsWildcard.click(); + + await indexToggleIsWildcard.waitForDisplayed(); + await browser.clickVisible(Selectors.IndexToggleIsWildcard); // set the text in the editor await browser.setAceValue( diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 5da6aac704d..1ae92b2fbc0 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -59,7 +59,6 @@ "@mongodb-js/compass-logging": "^1.0.0", "@mongodb-js/mongodb-redux-common": "^2.0.0", "bson": "^4.4.1", - "hadron-react-buttons": "^6.0.0", "hadron-react-components": "^6.0.0", "mongodb-index-model": "^4.0.0", "react": "^16.14.0" @@ -85,13 +84,13 @@ "lodash.map": "^4.6.0", "lodash.max": "^4.0.1", "lodash.pick": "^4.4.0", - "lodash.pluck": "^3.1.2", "mocha": "^8.4.0", + "mongodb": "^4.6.0", + "mongodb-data-service": "^22.0.0", "mongodb-query-parser": "^2.4.6", "numeral": "^2.0.6", "nyc": "^15.1.0", "prop-types": "^15.7.2", - "react-bootstrap": "^0.32.1", "react-dom": "^16.14.0", "react-redux": "^5.0.6", "redux": "^4.1.2", @@ -106,7 +105,6 @@ "@mongodb-js/compass-logging": "^1.0.0", "@mongodb-js/mongodb-redux-common": "^2.0.0", "bson": "^4.4.1", - "hadron-react-buttons": "^6.0.0", "hadron-react-components": "^6.0.0", "mongodb-index-model": "^4.0.0", "react": "^16.14.0" diff --git a/packages/compass-indexes/src/components/create-index-field/create-index-field.jsx b/packages/compass-indexes/src/components/create-index-field/create-index-field.jsx deleted file mode 100644 index 0f00c949af5..00000000000 --- a/packages/compass-indexes/src/components/create-index-field/create-index-field.jsx +++ /dev/null @@ -1,198 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import styles from './create-index-field.module.less'; -import { hasColumnstoreIndexesSupport } from '../../utils/has-columnstore-indexes-support'; - -import { - Combobox, - ComboboxOption, - Select, - Option, -} from '@mongodb-js/compass-components'; - -/** - * Current allowed types for indexes. - */ -const INDEX_TYPES = ['1 (asc)', '-1 (desc)', '2dsphere', 'text', 'columnstore']; - -/** - * Default values for field name and type as presented in the UI - */ -const DEFAULT_FIELD = { - name: 'Select or type a field name', - type: 'Select a type', -}; - -/** - * Component for the index field form. - */ -class CreateIndexField extends PureComponent { - static displayName = 'CreateIndexField'; - - static propTypes = { - fields: PropTypes.array.isRequired, - field: PropTypes.object.isRequired, - idx: PropTypes.number.isRequired, - serverVersion: PropTypes.string.isRequired, - disabledFields: PropTypes.array.isRequired, - isRemovable: PropTypes.bool.isRequired, - addField: PropTypes.func.isRequired, - removeField: PropTypes.func.isRequired, - updateFieldName: PropTypes.func.isRequired, - updateFieldType: PropTypes.func.isRequired, - newIndexField: PropTypes.string, - createNewIndexField: PropTypes.func.isRequired, - }; - - /** - * Create React dropdown items for each element in the fields array. - * - * @returns {Array} The React components for each item in the field and type dropdowns. - */ - getDropdownFieldsSelect() { - return this.props.fields.map((elem) => ({ - value: elem, - label: elem, - disabled: this.props.disabledFields.some((field) => field === elem), - })); - } - - /** - * Create React dropdown items for each element in the INDEX_TYPES array. - * - * @returns {Array} The React components for each item in the field and type dropdowns. - */ - getDropdownTypes() { - if (!hasColumnstoreIndexesSupport(this.props.serverVersion)) { - return INDEX_TYPES.filter((elem) => elem !== 'columnstore').map( - (elem) => ( - - ) - ); - } - - return INDEX_TYPES.map((elem) => ( - - )); - } - - /** - * Set state to selected field on name change. - * - * @param {object} name - The selected field name. - */ - selectFieldName(name) { - if (name !== null) { - this.props.updateFieldName(this.props.idx, name); - } - } - - /** - * Set state to selected field on type change. - * - * @param {string} type - The selected field type. - */ - selectFieldType(type) { - this.props.updateFieldType(this.props.idx, type); - } - - /** - * Remove this index field - * - * @param {object} evt The click event. - */ - remove(evt) { - evt.preventDefault(); - evt.stopPropagation(); - this.props.removeField(this.props.idx); - } - - renderIndexOptions() { - const fields = this.props.fields.map((value, idx) => ( - - )); - - if ( - this.props.newIndexField && - !this.props.fields.includes(this.props.newIndexField) - ) { - const newIndexField = this.props.newIndexField; - - fields.push( - - ); - } - - return fields; - } - - /** - * Render the index field form. - * - * @returns {React.Component} The index field form. - */ - render() { - return ( -
-
- - {this.renderIndexOptions()} - -
-
- -
-
-