From 73ceb56ef354eb9862f3c7e9a2f34e4ea1c6fb82 Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Mon, 28 Apr 2025 14:36:55 -0400 Subject: [PATCH 1/3] added covered queries and optimal queries --- .../create-index-form/index-flow-section.tsx | 86 +++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) 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..0727a621d85 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 @@ -78,6 +78,68 @@ export type IndexFlowSectionProps = { collectionName: string; }; +const generateCoveredQueries = ( + coveredQueriesArr: Array> +) => { + const examples = []; + let i = 0; + while (i < coveredQueriesArr.length) { + const currentRow = Object.assign({}, ...coveredQueriesArr.slice(0, i + 1)); + examples.push(currentRow); + i++; + } + + return examples; +}; + +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, @@ -107,6 +169,13 @@ const IndexFlowSection = ({ {} ); + const coveredQueriesArr = fields.map((field, index) => { + return { [field.name]: index + 1 }; + }); + + const coveredQueriesExamples = generateCoveredQueries(coveredQueriesArr); + const optimalQueriesExamples = generateOptimalQueries(coveredQueriesArr); + return (
- {/* Covered Queries, clean up with actual covered queries examples in CLOUDP-311782 */} + {/* Covered Queries */} - {`{ awards.wins:3 }`}
- {`{ awards.wins:3, imdb.rating:5 }`}
- {`{ awards.wins:3, imdb.rating:5, awards.nominations:8 }`}
+ {coveredQueriesExamples.map((example, index) => ( + + {JSON.stringify(example)} +
+
+ ))} +

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 */} + {/* Optimal queries */} - {`{ awards.wins : 5, imdb.rating: {$gt : 5} }.sort({ awards.nominations : 1 }`} + {optimalQueriesExamples}

- Learn More From 175ebaa124aedbe8691828907e84ccaf143555d1 Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Mon, 28 Apr 2025 15:41:29 -0400 Subject: [PATCH 2/3] polished up and added tests --- .../index-flow-section.spec.tsx | 176 ++++++++++++++---- .../create-index-form/index-flow-section.tsx | 174 +++++++++-------- 2 files changed, 241 insertions(+), 109 deletions(-) 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 0727a621d85..c579dc023c9 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,6 +10,7 @@ import { Toggle, fontFamilies, InfoSprinkle, + Tooltip, } from '@mongodb-js/compass-components'; import React, { useState } from 'react'; import type { Field } from '../../modules/create-index'; @@ -81,15 +82,17 @@ export type IndexFlowSectionProps = { const generateCoveredQueries = ( coveredQueriesArr: Array> ) => { - const examples = []; - let i = 0; - while (i < coveredQueriesArr.length) { + const rows = []; + for (let i = 0; i < coveredQueriesArr.length; i++) { const currentRow = Object.assign({}, ...coveredQueriesArr.slice(0, i + 1)); - examples.push(currentRow); - i++; + rows.push( + <> + {JSON.stringify(currentRow)}
+ + ); } - return examples; + return <>{rows}; }; const generateOptimalQueries = ( @@ -99,7 +102,7 @@ const generateOptimalQueries = ( // Do not show for 1 field or less if (numOfFields < 2) { - return; + return ''; } const lastField = coveredQueriesArr[numOfFields - 1]; @@ -153,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) => { @@ -173,8 +177,21 @@ const IndexFlowSection = ({ return { [field.name]: index + 1 }; }); - const coveredQueriesExamples = generateCoveredQueries(coveredQueriesArr); - const optimalQueriesExamples = generateOptimalQueries(coveredQueriesArr); + const [coveredQueriesExamples, setCoveredQueriesExamples] = + useState(<>); + const [optimalQueriesExamples, setOptimalQueriesExamples] = useState< + string | JSX.Element + >(''); + const [showCoveredQueries, setShowCoveredQueries] = useState(false); + + const onCoveredQueriesButtonClick = () => { + generateCoveredQueries(coveredQueriesArr); + generateOptimalQueries(coveredQueriesArr); + + setCoveredQueriesExamples(generateCoveredQueries(coveredQueriesArr)); + setOptimalQueriesExamples(generateOptimalQueries(coveredQueriesArr)); + setShowCoveredQueries(true); + }; return (
@@ -214,70 +231,81 @@ const IndexFlowSection = ({ )}
- + } + align="top" + justify="middle" + enabled={hasUnsupportedQueryTypes} > - Show covered queries - + Example queries are unavailable for 2dsphere and text +
- -
- - 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. - -
- -
- {/* Covered Queries */} - - {coveredQueriesExamples.map((example, index) => ( - - {JSON.stringify(example)} -
-
- ))} - - -

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

- {optimalQueriesExamples} - -

- - Learn More - -
+ + 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. + +
+ +
+ {/* Covered Queries */} + + {coveredQueriesExamples} + + + {!!optimalQueriesExamples && ( + <> +

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

+ + Learn More + + + )} +
+ + )}
); }; From 47d9f8d62078cf22d9e8f8bc4441fb3f086bb70b Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Tue, 29 Apr 2025 11:07:20 -0400 Subject: [PATCH 3/3] updated state to be an object and changed to useCallback for button click --- .../create-index-form/index-flow-section.tsx | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) 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 c579dc023c9..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 @@ -12,7 +12,7 @@ import { 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'; @@ -173,25 +173,30 @@ const IndexFlowSection = ({ {} ); - const coveredQueriesArr = fields.map((field, index) => { - return { [field.name]: index + 1 }; + const [coveredQueriesObj, setCoveredQueriesObj] = useState<{ + coveredQueries: JSX.Element; + optimalQueries: string | JSX.Element; + showCoveredQueries: boolean; + }>({ + coveredQueries: <>, + optimalQueries: '', + showCoveredQueries: false, }); - const [coveredQueriesExamples, setCoveredQueriesExamples] = - useState(<>); - const [optimalQueriesExamples, setOptimalQueriesExamples] = useState< - string | JSX.Element - >(''); - const [showCoveredQueries, setShowCoveredQueries] = useState(false); + const onCoveredQueriesButtonClick = useCallback(() => { + const coveredQueriesArr = fields.map((field, index) => { + return { [field.name]: index + 1 }; + }); - const onCoveredQueriesButtonClick = () => { - generateCoveredQueries(coveredQueriesArr); - generateOptimalQueries(coveredQueriesArr); + setCoveredQueriesObj({ + coveredQueries: generateCoveredQueries(coveredQueriesArr), + optimalQueries: generateOptimalQueries(coveredQueriesArr), + showCoveredQueries: true, + }); + }, [fields]); - setCoveredQueriesExamples(generateCoveredQueries(coveredQueriesArr)); - setOptimalQueriesExamples(generateOptimalQueries(coveredQueriesArr)); - setShowCoveredQueries(true); - }; + const { coveredQueries, optimalQueries, showCoveredQueries } = + coveredQueriesObj; return (
@@ -280,10 +285,10 @@ const IndexFlowSection = ({ className={codeStyles} data-testid="index-flow-section-covered-queries-examples" > - {coveredQueriesExamples} + {coveredQueries} - {!!optimalQueriesExamples && ( + {!!optimalQueries && ( <>

@@ -295,7 +300,7 @@ const IndexFlowSection = ({ className={codeStyles} data-testid="index-flow-section-optimal-queries-examples" > - {optimalQueriesExamples} + {optimalQueries}