Skip to content

feat: Fetching index suggestions and dealing with the states CLOUDP-311786 #6887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 7, 2025
266 changes: 167 additions & 99 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/compass-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@leafygreen-ui/search-input": "^5.0.2",
"@leafygreen-ui/segmented-control": "^10.0.2",
"@leafygreen-ui/select": "^14.0.2",
"@leafygreen-ui/skeleton-loader": "^2.0.11",
"@leafygreen-ui/split-button": "^4.1.5",
"@leafygreen-ui/table": "^13.0.1",
"@leafygreen-ui/tabs": "^14.0.2",
Expand Down
1 change: 1 addition & 0 deletions packages/compass-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,4 @@ export {
type ItemRenderer as VirtualListItemRenderer,
} from './components/virtual-list';
export { SelectTable } from './components/select-table';
export { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader';
1 change: 1 addition & 0 deletions packages/compass-indexes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"mongodb": "^6.14.1",
"mongodb-collection-model": "^5.26.0",
"mongodb-data-service": "^22.25.8",
"mongodb-mql-engines": "^0.0.4",
"mongodb-query-parser": "^4.3.0",
"mongodb-ns": "^2.4.2",
"numeral": "^2.0.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ const generateCode = ({
}: {
dbName: string;
collectionName: string;
indexNameTypeMap: { [key: string]: string };
indexNameTypeMap: Record<string, string | number>;
}) => {
let codeStr = `db.getSiblingDB("${dbName}").getCollection("${escapeText(
collectionName
)}").createIndex({\n`;

Object.entries(indexNameTypeMap).forEach(([name, type], index) => {
// Replacing everything inside the parenthesis i.e. (asc)
let parsedType = escapeText(type.replace(/\(.*?\)/g, '')).trim();
let parsedType = escapeText(`${type}`.replace(/\(.*?\)/g, '')).trim();
if (!NUMERIC_INDEX_TYPES.includes(Number(parsedType))) {
parsedType = `"${parsedType}"`;
}
Expand All @@ -59,7 +59,7 @@ const MDBCodeViewer = ({
}: {
dbName: string;
collectionName: string;
indexNameTypeMap: { [key: string]: string };
indexNameTypeMap: Record<string, string | number>;
dataTestId?: string;
}) => {
const GeneratedCode = generateCode({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,83 @@ import React from 'react';
import { render, screen } from '@mongodb-js/testing-library-compass';
import QueryFlowSection from './query-flow-section';
import { expect } from 'chai';
import { Provider } from 'react-redux';
import { setupStore } from '../../../test/setup-store';
import { ActionTypes } from '../../modules/create-index';

describe('QueryFlowSection', () => {
const store = setupStore();
const dbName = 'fakeDBName';
const collectionName = 'fakeCollectionName';
const renderComponent = () => {
render(
<QueryFlowSection
schemaFields={[]}
serverVersion="5.0.0"
dbName={'fakeDBName'}
collectionName={'fakeCollectionName'}
/>
<Provider store={store}>
<QueryFlowSection
schemaFields={[]}
serverVersion="5.0.0"
dbName={dbName}
collectionName={collectionName}
/>
</Provider>
);
};
it('renders the input query section with a code editor', () => {
renderComponent();
const codeEditor = screen.getByTestId('query-flow-section-code-editor');
expect(codeEditor).to.be.visible;

describe('in the initial state', () => {
beforeEach(() => {
renderComponent();
});
it('renders the input query section with a code editor', () => {
const codeEditor = screen.getByTestId('query-flow-section-code-editor');
expect(codeEditor).to.be.visible;
});

it('renders the "Show suggested index" button', () => {
const buttonElement = screen.getByText('Show suggested index');
expect(buttonElement).to.be.visible;
});
it('does not render the suggested index section with formatted index code', () => {
const codeElement = screen.queryByTestId(
'query-flow-section-suggested-index'
);
expect(codeElement).to.be.null;
});
});

it('renders the "Show suggested index" button', () => {
renderComponent();
const buttonElement = screen.getByText('Show suggested index');
expect(buttonElement).to.be.visible;
describe('when fetching for index suggestions', () => {
beforeEach(() => {
renderComponent();

store.dispatch({
type: ActionTypes.SuggestedIndexesRequested,
});
});
it('renders a loader for the code section', () => {
const loader = screen.getByTestId('query-flow-section-code-loader');
expect(loader).to.be.visible;
});
});

it('renders the suggested index section with formatted index code', () => {
renderComponent();
const codeElement = screen.getByTestId(
'query-flow-section-suggested-index'
);
expect(codeElement).to.be.visible;
describe('when index suggestions is fetched', () => {
beforeEach(() => {
renderComponent();

store.dispatch({
type: ActionTypes.SuggestedIndexesFetched,
sampleDocs: [],
indexSuggestions: { a: 1, b: 2 },
fetchingSuggestionsError: null,
indexSuggestionsState: 'success',
});
});

it('renders the suggested index section with formatted index code', () => {
const codeElement = screen.getByTestId(
'query-flow-section-suggested-index'
);
expect(codeElement).to.be.visible;
expect(codeElement).to.have.text(
`db.getSiblingDB("${dbName}").getCollection("${collectionName}").createIndex({ "a": 1, "b": "2"});`
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ import {
Body,
cx,
useFocusRing,
ParagraphSkeleton,
} from '@mongodb-js/compass-components';
import React, { useMemo } from 'react';
import React, { useMemo, useCallback } from 'react';
import { css, spacing } from '@mongodb-js/compass-components';
import {
CodemirrorMultilineEditor,
createQueryAutocompleter,
} from '@mongodb-js/compass-editor';
import MDBCodeViewer from './mdb-code-viewer';
import type { RootState } from '../../modules';
import { fetchIndexSuggestions } from '../../modules/create-index';
import type {
IndexSuggestionState,
SuggestedIndexFetchedProps,
} from '../../modules/create-index';
import { connect } from 'react-redux';

const inputQueryContainerStyles = css({
marginBottom: spacing[600],
display: 'flex',
flexDirection: 'column',
});
Expand Down Expand Up @@ -60,16 +67,34 @@ const codeEditorStyles = css({
},
});

const indexSuggestionsLoaderStyles = css({
marginBottom: spacing[600],
padding: spacing[600],
background: palette.gray.light3,
border: `1px solid ${palette.gray.light2}`,
borderRadius: editorContainerRadius,
});

const QueryFlowSection = ({
schemaFields,
serverVersion,
dbName,
collectionName,
onSuggestedIndexButtonClick,
indexSuggestions,
fetchingSuggestionsState,
}: {
schemaFields: { name: string; description?: string }[];
serverVersion: string;
dbName: string;
collectionName: string;
onSuggestedIndexButtonClick: ({
dbName,
collectionName,
inputQuery,
}: SuggestedIndexFetchedProps) => Promise<void>;
indexSuggestions: Record<string, number> | null;
fetchingSuggestionsState: IndexSuggestionState;
}) => {
const [inputQuery, setInputQuery] = React.useState('');
const completer = useMemo(
Expand All @@ -88,6 +113,18 @@ const QueryFlowSection = ({
radius: editorContainerRadius,
});

const handleSuggestedIndexButtonClick = useCallback(() => {
const sanitizedInputQuery = inputQuery.trim();

void onSuggestedIndexButtonClick({
dbName,
collectionName,
inputQuery: sanitizedInputQuery,
});
}, [inputQuery, dbName, collectionName, onSuggestedIndexButtonClick]);

const isFetchingIndexSuggestions = fetchingSuggestionsState === 'fetching';

return (
<>
<Body baseFontSize={16} weight="medium" className={headerStyles}>
Expand Down Expand Up @@ -116,30 +153,56 @@ const QueryFlowSection = ({

<div className={editorActionContainerStyles}>
<Button
onClick={() => {
// TODO in CLOUDP-311786
}}
onClick={handleSuggestedIndexButtonClick}
className={suggestedIndexButtonStyles}
size="small"
>
Show suggested index
</Button>
</div>
</div>
<Body baseFontSize={16} weight="medium" className={headerStyles}>
Suggested Index
</Body>{' '}
<div className={suggestedIndexContainerStyles}>
{/* TODO in CLOUDP-311786, replace hardcoded values with actual data */}
<MDBCodeViewer
dataTestId="query-flow-section-suggested-index"
dbName={dbName}
collectionName={collectionName}
indexNameTypeMap={{ 'awards.win': '1', 'imdb.rating': '1' }}

{(isFetchingIndexSuggestions || indexSuggestions) && (
<Body baseFontSize={16} weight="medium" className={headerStyles}>
Suggested Index
</Body>
)}

{isFetchingIndexSuggestions ? (
<ParagraphSkeleton
data-testid="query-flow-section-code-loader"
className={indexSuggestionsLoaderStyles}
/>
</div>
) : (
indexSuggestions && (
<>
<div className={suggestedIndexContainerStyles}>
<MDBCodeViewer
dataTestId="query-flow-section-suggested-index"
dbName={dbName}
collectionName={collectionName}
indexNameTypeMap={indexSuggestions}
/>
</div>
</>
)
)}
</>
);
};

export default QueryFlowSection;
const mapState = ({ createIndex }: RootState) => {
const { indexSuggestions, sampleDocs, fetchingSuggestionsState } =
createIndex;
return {
indexSuggestions,
sampleDocs,
fetchingSuggestionsState,
};
};

const mapDispatch = {
onSuggestedIndexButtonClick: fetchIndexSuggestions,
};

export default connect(mapState, mapDispatch)(QueryFlowSection);
Loading
Loading