diff --git a/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx b/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx index a11e5ae3343..22574ea61ee 100644 --- a/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx +++ b/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx @@ -2,61 +2,165 @@ import React from 'react'; import { render, screen } from '@mongodb-js/testing-library-compass'; import IndexFlowSection from './index-flow-section'; import { expect } from 'chai'; +import type { Field } from '../../modules/create-index'; describe('IndexFlowSection', () => { - const renderComponent = (createIndexFieldsComponent?: JSX.Element) => { + const renderComponent = ({ + createIndexFieldsComponent, + fields, + }: { + createIndexFieldsComponent?: JSX.Element; + fields?: Field[]; + }) => { render( ); }; - it('renders the Input Index header', () => { - renderComponent(); - expect(screen.getByText('Input Index')).to.be.visible; - }); - it('renders the Code Equivalent toggle', () => { - renderComponent(); - expect(screen.getByLabelText('Toggle Code Equivalent')).to.be.visible; - }); + describe('when the fields are not filled in', () => { + it('renders the Input Index header', () => { + renderComponent({}); + expect(screen.getByText('Input Index')).to.be.visible; + }); - it('renders the Show covered queries button', () => { - renderComponent(); - expect(screen.getByText('Show covered queries')).to.be.visible; - }); + it('does not render the Covered Queries header', () => { + renderComponent({}); + expect(screen.queryByText('Covered Queries')).to.be.null; + }); - it('renders the Covered Queries header', () => { - renderComponent(); - expect(screen.getByText('Covered Queries')).to.be.visible; - }); + it('renders the Code Equivalent toggle', () => { + renderComponent({}); + expect(screen.getByLabelText('Toggle Code Equivalent')).to.be.visible; + }); - it('renders the provided createIndexFieldsComponent', () => { - const mockComponent = ( -
Mock Component
- ); - renderComponent(mockComponent); - expect(screen.getByTestId('mock-component')).to.be.visible; + it('renders the Show covered queries button and it\\s disabled', () => { + renderComponent({}); + const coveredQueriesButton = screen.getByTestId( + 'index-flow-section-covered-queries-button' + ); + + expect(coveredQueriesButton).to.be.visible; + }); + + it('does not render the covered queries examples', () => { + renderComponent({}); + expect( + screen.queryByTestId('index-flow-section-covered-queries-examples') + ).not.to.exist; + }); + + it('does not render the optimal query examples', () => { + renderComponent({}); + expect( + screen.queryByTestId('index-flow-section-optimal-queries-examples') + ).not.to.exist; + }); + + it('renders the provided createIndexFieldsComponent', () => { + const mockComponent = ( +
Mock Component
+ ); + renderComponent({ createIndexFieldsComponent: mockComponent }); + expect(screen.getByTestId('mock-component')).to.be.visible; + }); }); - it('renders the covered queries examples', () => { - renderComponent(); - expect(screen.getByTestId('index-flow-section-covered-queries-examples')).to - .exist; + describe('when 3 index fields are filled in and user clicks on covered queries button', () => { + const fields: Field[] = [ + { name: 'field1', type: '1 (asc)' }, + { name: 'field2', type: '-1 (desc)' }, + { name: 'field3', type: '1 (asc)' }, + ]; + + beforeEach(() => { + renderComponent({ fields }); + screen.getByTestId('index-flow-section-covered-queries-button').click(); + }); + + it('renders the covered queries examples', () => { + const coveredQueriesExamples = screen.getByTestId( + 'index-flow-section-covered-queries-examples' + ); + expect(coveredQueriesExamples).to.exist; + expect(coveredQueriesExamples).to.contain.text( + JSON.stringify({ + field1: 1, + field2: 2, + field3: 3, + }) + ); + }); + + it('renders the optimal query examples', () => { + const optimalQueriesExamples = screen.getByTestId( + 'index-flow-section-optimal-queries-examples' + ); + expect(optimalQueriesExamples).to.exist; + expect(optimalQueriesExamples).to.contain.text( + `{"field1":1,"field2":{"$gt":2}}.sort(field3: 1})` + ); + }); + + it('renders the Learn More link', () => { + const link = screen.getByText('Learn More'); + expect(link).to.be.visible; + }); }); - it('renders the optimal query examples', () => { - renderComponent(); - expect(screen.getByTestId('index-flow-section-optimal-query-examples')).to - .exist; + describe('when 2 index fields are filled in and user clicks on covered queries button', () => { + const fields: Field[] = [ + { name: 'field1', type: '1 (asc)' }, + { name: 'field2', type: '1 (asc)' }, + ]; + + beforeEach(() => { + renderComponent({ fields }); + screen.getByTestId('index-flow-section-covered-queries-button').click(); + }); + + it('renders the covered queries examples', () => { + const coveredQueriesExamples = screen.getByTestId( + 'index-flow-section-covered-queries-examples' + ); + expect(coveredQueriesExamples).to.exist; + }); + + it('renders the optimal query examples', () => { + const optimalQueriesExamples = screen.getByTestId( + 'index-flow-section-optimal-queries-examples' + ); + expect(optimalQueriesExamples).to.exist; + expect(optimalQueriesExamples).to.contain.text( + `{"field1":1,"field2":{"$gt":2}}}` + ); + expect(optimalQueriesExamples).to.contain.text( + `{"field1":1}.sort({"field2":2})` + ); + }); }); - it('renders the Learn More link', () => { - renderComponent(); - const link = screen.getByText('Learn More'); - expect(link).to.be.visible; + describe('when 1 index field is filled in and user clicks on covered queries button', () => { + const fields: Field[] = [{ name: 'field1', type: '1 (asc)' }]; + + beforeEach(() => { + renderComponent({ fields }); + screen.getByTestId('index-flow-section-covered-queries-button').click(); + }); + + it('renders the covered queries examples', () => { + expect(screen.getByTestId('index-flow-section-covered-queries-examples')) + .to.exist; + }); + + it('does not render the optimal query examples', () => { + expect( + screen.queryByTestId('index-flow-section-optimal-queries-examples') + ).not.to.exist; + }); }); }); diff --git a/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx index 376eb5deabc..e367e3629d3 100644 --- a/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx @@ -10,8 +10,9 @@ import { Toggle, fontFamilies, InfoSprinkle, + Tooltip, } from '@mongodb-js/compass-components'; -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import type { Field } from '../../modules/create-index'; import MDBCodeViewer from './mdb-code-viewer'; @@ -78,6 +79,70 @@ export type IndexFlowSectionProps = { collectionName: string; }; +const generateCoveredQueries = ( + coveredQueriesArr: Array> +) => { + const rows = []; + for (let i = 0; i < coveredQueriesArr.length; i++) { + const currentRow = Object.assign({}, ...coveredQueriesArr.slice(0, i + 1)); + rows.push( + <> + {JSON.stringify(currentRow)}
+ + ); + } + + return <>{rows}; +}; + +const generateOptimalQueries = ( + coveredQueriesArr: Array> +) => { + const numOfFields = coveredQueriesArr.length; + + // Do not show for 1 field or less + if (numOfFields < 2) { + return ''; + } + + const lastField = coveredQueriesArr[numOfFields - 1]; + const lastFieldKey = Object.keys(lastField)[0]; + + // If there are only two fields, we want to show two examples + // i.e. {a:1, b: {$gt:2}} and {a:1}.sort({b: 2}) + if (numOfFields === 2) { + const firstField = coveredQueriesArr[0]; + const firstFieldKey = Object.keys(firstField)[0]; + + return ( + <> + {`{"${firstFieldKey}":1,"${lastFieldKey}":{"$gt":2}}}`} +
+ {`{"${firstFieldKey}":1}.sort({"${lastFieldKey}":2})`} + + ); + } + + // If there are more than two fields, we want to show a longer optimal query with gt and sort + // i.e. {a:1, b:2, c:{gt:3}}.sort({d:1}) + const optimalQueries = coveredQueriesArr + .slice(0, -1) + .reduce>((acc, obj, index) => { + const key = Object.keys(obj)[0]; + const value = obj[key]; + + if (index === numOfFields - 2) { + acc[key] = { $gt: value }; + } else { + acc[key] = value; + } + + return acc; + }, {}); + + return JSON.stringify(optimalQueries) + `.sort(${lastFieldKey}: 1})`; +}; + const IndexFlowSection = ({ createIndexFieldsComponent, fields, @@ -91,11 +156,12 @@ const IndexFlowSection = ({ return field.name && field.type; }); + const hasUnsupportedQueryTypes = fields.some((field) => { + return field.type === '2dsphere' || field.type === 'text'; + }); + const isCoveredQueriesButtonDisabled = - !areAllFieldsFilledIn || - fields.some((field) => { - return field.type === '2dsphere' || field.type === 'text'; - }); + !areAllFieldsFilledIn || hasUnsupportedQueryTypes; const indexNameTypeMap = fields.reduce>( (accumulator, currentValue) => { @@ -107,6 +173,31 @@ const IndexFlowSection = ({ {} ); + const [coveredQueriesObj, setCoveredQueriesObj] = useState<{ + coveredQueries: JSX.Element; + optimalQueries: string | JSX.Element; + showCoveredQueries: boolean; + }>({ + coveredQueries: <>, + optimalQueries: '', + showCoveredQueries: false, + }); + + const onCoveredQueriesButtonClick = useCallback(() => { + const coveredQueriesArr = fields.map((field, index) => { + return { [field.name]: index + 1 }; + }); + + setCoveredQueriesObj({ + coveredQueries: generateCoveredQueries(coveredQueriesArr), + optimalQueries: generateOptimalQueries(coveredQueriesArr), + showCoveredQueries: true, + }); + }, [fields]); + + const { coveredQueries, optimalQueries, showCoveredQueries } = + coveredQueriesObj; + return (
- + } + align="top" + justify="middle" + enabled={hasUnsupportedQueryTypes} > - Show covered queries - + Example queries are unavailable for 2dsphere and text +
+ {showCoveredQueries && ( + <> +
+ + Covered Queries + -
- - Covered Queries - - - - {' '} - A covered query is a query that can be satisfied entirely using an - index and does not have to examine any documents. If a query is - covered, it is highly performant. - -
+ + A covered query is a query that can be satisfied entirely using an + index and does not have to examine any documents. If a query is + covered, it is highly performant. + +
-
- {/* Covered Queries, clean up with actual covered queries examples in CLOUDP-311782 */} - - {`{ awards.wins:3 }`}
- {`{ awards.wins:3, imdb.rating:5 }`}
- {`{ awards.wins:3, imdb.rating:5, awards.nominations:8 }`}
- -

- - Follow the Equality, Sort, Range (ESR) Rule. This index is optimal - for queries that have this pattern: - - {/* Optimal queries, clean up with actual optimal queries in CLOUDP-311783 */} - - {`{ awards.wins : 5, imdb.rating: {$gt : 5} }.sort({ awards.nominations : 1 }`} - -

+
+ {/* Covered Queries */} + + {coveredQueries} + - - Learn More - -
+ {!!optimalQueries && ( + <> +

+ + Follow the Equality, Sort, Range (ESR) Rule. This index is + optimal for queries that have this pattern: + + {/* Optimal queries */} + + {optimalQueries} + +

+ + Learn More + + + )} +
+ + )} ); };