From 053ffd438d707fb3ef4d735293bf8af069593b5c Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Tue, 29 Apr 2025 16:02:52 -0400 Subject: [PATCH 01/13] use mql wip --- package-lock.json | 31 +++++++++ package.json | 3 + .../create-index-form/mdb-code-viewer.tsx | 2 +- .../create-index-form/query-flow-section.tsx | 63 ++++++++++++++----- 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index d72d2d5584a..9df33578540 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "configs/*", "scripts" ], + "dependencies": { + "mongodb-mql-engines": "^0.0.3" + }, "devDependencies": { "@mongodb-js/monorepo-tools": "^1.1.16", "@mongodb-js/sbom-tools": "^0.7.2", @@ -23686,6 +23689,12 @@ "node": ">= 0.12" } }, + "node_modules/format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==", + "license": "MIT" + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -31637,6 +31646,15 @@ "bson": "6.x" } }, + "node_modules/mongodb-mql-engines": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mongodb-mql-engines/-/mongodb-mql-engines-0.0.3.tgz", + "integrity": "sha512-lJXvdYw5Xozwguboyr3PzVkK9EyQdVnzD3qA3i8OY8yi1QZ7YOm7EKhoMnqUV76ZuMRWLjHSZwwzAfEDhKn2+g==", + "license": "Apache-2.0", + "dependencies": { + "format-util": "^1.0.5" + } + }, "node_modules/mongodb-ns": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.4.2.tgz", @@ -72764,6 +72782,11 @@ "mime-types": "^2.1.12" } }, + "format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==" + }, "formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -80139,6 +80162,14 @@ "heap-js": "^2.3.0" } }, + "mongodb-mql-engines": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mongodb-mql-engines/-/mongodb-mql-engines-0.0.3.tgz", + "integrity": "sha512-lJXvdYw5Xozwguboyr3PzVkK9EyQdVnzD3qA3i8OY8yi1QZ7YOm7EKhoMnqUV76ZuMRWLjHSZwwzAfEDhKn2+g==", + "requires": { + "format-util": "^1.0.5" + } + }, "mongodb-ns": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.4.2.tgz", diff --git a/package.json b/package.json index d25b01690f9..9c25534765c 100644 --- a/package.json +++ b/package.json @@ -103,5 +103,8 @@ "chai-enzyme": { "cheerio": "1.0.0-rc.10" } + }, + "dependencies": { + "mongodb-mql-engines": "^0.0.3" } } diff --git a/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx b/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx index 44c4c9d1900..922130deac2 100644 --- a/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx +++ b/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx @@ -32,7 +32,7 @@ const generateCode = ({ 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}"`; } diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx index 2beb70201ce..4c8a6893448 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx @@ -5,13 +5,15 @@ import { cx, useFocusRing, } from '@mongodb-js/compass-components'; -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; +import { EJSON } from 'bson'; import { css, spacing } from '@mongodb-js/compass-components'; import { CodemirrorMultilineEditor, createQueryAutocompleter, } from '@mongodb-js/compass-editor'; import MDBCodeViewer from './mdb-code-viewer'; +import * as mql from 'mongodb-mql-engines'; const inputQueryContainerStyles = css({ marginBottom: spacing[600], @@ -88,6 +90,35 @@ const QueryFlowSection = ({ radius: editorContainerRadius, }); + // const sampleDocuments: Array = await dataService.sample( + // `${dbName}.${collectionName}`, + // { size: 50 } + // ); + + // const sampleDocuments = [ + // { a: 1, b: 2 }, + // { a: 5, b: true }, + // ]; + + const [indexNameTypeMap, setIndexNameTypeMap] = React.useState< + Record | undefined + >(); + const onSuggestedIndexesButtonClick = useCallback(async () => { + const namespace = mql.analyzeNamespace( + { database: dbName, collection: collectionName }, + [] + ); + const query = mql.parseQuery( + EJSON.parse(inputQuery, { relaxed: false }), + namespace + ); + const results = await mql.suggestIndex([query]); + console.log('index', results.index); + setIndexNameTypeMap(results?.index); + + console.log({ inputQuery, query }); + }, [dbName, collectionName, inputQuery]); + return ( <> @@ -117,7 +148,7 @@ const QueryFlowSection = ({
- - Suggested Index - {' '} -
- {/* TODO in CLOUDP-311786, replace hardcoded values with actual data */} - -
+ {indexNameTypeMap && ( + <> + + Suggested Index + {' '} +
+ {/* TODO in CLOUDP-311786, replace hardcoded values with actual data */} + +
+ + )} ); }; From 1ef77508a9d3b84feff7b86768b85de242522f5c Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Wed, 30 Apr 2025 14:11:00 -0400 Subject: [PATCH 02/13] wip with setting up some actions --- .../create-index-form/mdb-code-viewer.tsx | 4 +- .../create-index-form/query-flow-section.tsx | 97 +++++++++++++------ .../src/modules/create-index.tsx | 96 +++++++++++++++++- 3 files changed, 162 insertions(+), 35 deletions(-) diff --git a/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx b/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx index 922130deac2..e3d7f46f913 100644 --- a/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx +++ b/packages/compass-indexes/src/components/create-index-form/mdb-code-viewer.tsx @@ -24,7 +24,7 @@ const generateCode = ({ }: { dbName: string; collectionName: string; - indexNameTypeMap: { [key: string]: string }; + indexNameTypeMap: Record; }) => { let codeStr = `db.getSiblingDB("${dbName}").getCollection("${escapeText( collectionName @@ -59,7 +59,7 @@ const MDBCodeViewer = ({ }: { dbName: string; collectionName: string; - indexNameTypeMap: { [key: string]: string }; + indexNameTypeMap: Record; dataTestId?: string; }) => { const GeneratedCode = generateCode({ diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx index 4c8a6893448..74b766da121 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx @@ -14,6 +14,13 @@ import { } from '@mongodb-js/compass-editor'; import MDBCodeViewer from './mdb-code-viewer'; import * as mql from 'mongodb-mql-engines'; +import type { RootState } from '../../modules'; +import { suggestedIndexFetched } from '../../modules/create-index'; +import type { + IndexSuggestionState, + SuggestedIndexFetchedProps, +} from '../../modules/create-index'; +import { connect } from 'react-redux'; const inputQueryContainerStyles = css({ marginBottom: spacing[600], @@ -67,11 +74,19 @@ const QueryFlowSection = ({ serverVersion, dbName, collectionName, + onSuggestedIndexButtonClick, + indexSuggestions, }: { schemaFields: { name: string; description?: string }[]; serverVersion: string; dbName: string; collectionName: string; + onSuggestedIndexButtonClick: ({ + indexSuggestions, + error, + indexSuggestionsState, + }: SuggestedIndexFetchedProps) => void; + indexSuggestions: Record | null; }) => { const [inputQuery, setInputQuery] = React.useState(''); const completer = useMemo( @@ -90,34 +105,41 @@ const QueryFlowSection = ({ radius: editorContainerRadius, }); - // const sampleDocuments: Array = await dataService.sample( - // `${dbName}.${collectionName}`, - // { size: 50 } - // ); - - // const sampleDocuments = [ - // { a: 1, b: 2 }, - // { a: 5, b: true }, - // ]; - - const [indexNameTypeMap, setIndexNameTypeMap] = React.useState< - Record | undefined - >(); - const onSuggestedIndexesButtonClick = useCallback(async () => { - const namespace = mql.analyzeNamespace( - { database: dbName, collection: collectionName }, - [] - ); - const query = mql.parseQuery( - EJSON.parse(inputQuery, { relaxed: false }), - namespace - ); - const results = await mql.suggestIndex([query]); - console.log('index', results.index); - setIndexNameTypeMap(results?.index); - - console.log({ inputQuery, query }); - }, [dbName, collectionName, inputQuery]); + const handleSuggestedIndexButtonClick = useCallback(async () => { + try { + const sanitizedInputQuery = inputQuery.trim(); + const namespace = mql.analyzeNamespace( + { database: dbName, collection: collectionName }, + [] + ); + const query = mql.parseQuery( + EJSON.parse(sanitizedInputQuery, { relaxed: false }), + namespace + ); + const results = await mql.suggestIndex([query]); + + if (results?.index) { + onSuggestedIndexButtonClick({ + indexSuggestions: results.index, + error: null, + indexSuggestionsState: 'success', + }); + } else { + onSuggestedIndexButtonClick({ + indexSuggestions: null, + error: + 'No suggested index found. Please choose Start with an Index at the top to continue.', + indexSuggestionsState: 'error', + }); + } + } catch (error) { + onSuggestedIndexButtonClick({ + indexSuggestions: null, + error: 'Error parsing query. Please follow query structure.', + indexSuggestionsState: 'error', + }); + } + }, [inputQuery, dbName, collectionName, onSuggestedIndexButtonClick]); return ( <> @@ -148,7 +170,7 @@ const QueryFlowSection = ({
- {indexNameTypeMap && ( + {indexSuggestions && ( <> Suggested Index @@ -168,7 +190,7 @@ const QueryFlowSection = ({ dataTestId="query-flow-section-suggested-index" dbName={dbName} collectionName={collectionName} - indexNameTypeMap={indexNameTypeMap} + indexNameTypeMap={indexSuggestions} /> @@ -177,4 +199,15 @@ const QueryFlowSection = ({ ); }; -export default QueryFlowSection; +const mapState = ({ createIndex }: RootState) => { + const { indexSuggestions } = createIndex; + return { + indexSuggestions, + }; +}; + +const mapDispatch = { + onSuggestedIndexButtonClick: suggestedIndexFetched, +}; + +export default connect(mapState, mapDispatch)(QueryFlowSection); diff --git a/packages/compass-indexes/src/modules/create-index.tsx b/packages/compass-indexes/src/modules/create-index.tsx index ad78e8da201..8aeb2c4b8c0 100644 --- a/packages/compass-indexes/src/modules/create-index.tsx +++ b/packages/compass-indexes/src/modules/create-index.tsx @@ -27,6 +27,9 @@ export enum ActionTypes { CreateIndexFormSubmitted = 'compass-indexes/create-index/create-index-form-submitted', TabUpdated = 'compass-indexes/create-index/tab-updated', + + SuggestedIndexesRequested = 'compass-indexes/create-index/suggested-indexes-requested', + SuggestedIndexesFetched = 'compass-indexes/create-index/suggested-indexes-fetched', } // fields @@ -274,7 +277,7 @@ const INITIAL_OPTIONS_STATE = Object.fromEntries( }) ) as Options; -// other +export type IndexSuggestionState = 'initial' | 'fetching' | 'success' | 'error'; export type State = { // A unique id assigned to the create index modal on open, will be used when @@ -296,6 +299,10 @@ export type State = { // current tab that user is on (Query Flow or Index Flow) currentTab: Tab; + + fetchingSuggestionsState: IndexSuggestionState; + fetchingSuggestionsError: Error | string | null; + indexSuggestions: Record | null; }; export const INITIAL_STATE: State = { @@ -305,6 +312,9 @@ export const INITIAL_STATE: State = { fields: INITIAL_FIELDS_STATE, options: INITIAL_OPTIONS_STATE, currentTab: 'IndexFlow', + fetchingSuggestionsState: 'initial', + fetchingSuggestionsError: null, + indexSuggestions: null, }; function getInitialState(): State { @@ -337,6 +347,62 @@ export type CreateIndexSpec = { [key: string]: string | number; }; +type SuggestedIndexesRequestedAction = { + type: ActionTypes.SuggestedIndexesRequested; +}; + +export const suggestedIndexesRequested = + (): SuggestedIndexesRequestedAction => ({ + type: ActionTypes.SuggestedIndexesRequested, + }); + +export type SuggestedIndexFetchedAction = { + type: ActionTypes.SuggestedIndexesFetched; + sampleDocs: Array; + indexSuggestions: { [key: string]: number } | null; + error: string | null; + indexSuggestionsState: IndexSuggestionState; +}; + +export type SuggestedIndexFetchedProps = { + indexSuggestions: { [key: string]: number } | null; + error: string | null; + indexSuggestionsState: IndexSuggestionState; +}; + +// TODO: dispatch sample docs in here +export const suggestedIndexFetched = ({ + indexSuggestions, + error, + indexSuggestionsState, +}: { + indexSuggestions: { [key: string]: number } | null; + error: string | null; + indexSuggestionsState: IndexSuggestionState; +}): IndexesThunkAction< + void, + SuggestedIndexFetchedAction | ErrorEncounteredAction +> => { + return (dispatch, getState, { track, preferences }) => { + if (error) { + dispatch(errorEncountered(error)); + return; + } + + dispatch({ + type: ActionTypes.SuggestedIndexesFetched, + sampleDocs: [], + indexSuggestions, + error, + indexSuggestionsState, + }); + + if (error) { + dispatch(errorEncountered(error)); + } + }; +}; + function isEmptyValue(value: unknown) { if (value === '') { return true; @@ -629,6 +695,34 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { }; } + if ( + isAction( + action, + ActionTypes.SuggestedIndexesRequested + ) + ) { + return { + ...state, + fetchingSuggestionsState: 'fetching', + fetchingSuggestionsError: null, + indexSuggestions: null, + }; + } + + if ( + isAction( + action, + ActionTypes.SuggestedIndexesFetched + ) + ) { + return { + ...state, + fetchingSuggestionsState: action.indexSuggestionsState, + fetchingSuggestionsError: action.error, + indexSuggestions: action.indexSuggestions, + }; + } + return state; }; From ab417e38b2cbc4365e667a0db7fb2a25e85bab02 Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Thu, 1 May 2025 12:42:00 -0400 Subject: [PATCH 03/13] fetch sample + cleaned up index suggestions, added loader --- package-lock.json | 237 ++++++++++-------- package.json | 1 + packages/compass-components/src/index.ts | 1 + .../create-index-form/query-flow-section.tsx | 110 ++++---- .../src/modules/create-index.tsx | 116 ++++++--- packages/compass-indexes/src/stores/store.ts | 1 + 6 files changed, 275 insertions(+), 191 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9df33578540..525665d5b97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "scripts" ], "dependencies": { + "@leafygreen-ui/skeleton-loader": "^2.0.11", "mongodb-mql-engines": "^0.0.3" }, "devDependencies": { @@ -5430,20 +5431,20 @@ } }, "node_modules/@leafygreen-ui/card": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/card/-/card-12.0.2.tgz", - "integrity": "sha512-4ffWqzDG3u4APPH4d8nWfdrE7xXedGOvbIAJK2JGhsZcSTLaUpxOF8MYTgYDzRLnRye3bhLA/Om7AUY28aGsMA==", + "version": "12.0.9", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/card/-/card-12.0.9.tgz", + "integrity": "sha512-Go3ys8cimZQ8yStVt/++/jiK4O/r5cRqYXyM8ZAEH16nVTgMaumtSjiEiCuF1jWO2WaNCf9fNU4A8WOi7WZvUw==", "license": "Apache-2.0", "dependencies": { - "@leafygreen-ui/emotion": "^4.0.9", - "@leafygreen-ui/lib": "^14.0.2", - "@leafygreen-ui/palette": "^4.1.3", - "@leafygreen-ui/polymorphic": "^2.0.5", - "@leafygreen-ui/tokens": "^2.11.3", + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", + "@leafygreen-ui/polymorphic": "^2.0.9", + "@leafygreen-ui/tokens": "^2.12.2", "polished": "^4.2.2" }, "peerDependencies": { - "@leafygreen-ui/leafygreen-provider": "^4.0.2" + "@leafygreen-ui/leafygreen-provider": "^4.0.7" } }, "node_modules/@leafygreen-ui/checkbox": { @@ -5575,9 +5576,9 @@ } }, "node_modules/@leafygreen-ui/emotion": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.0.9.tgz", - "integrity": "sha512-f9ffU9FbIRi5KeOGO1YfAScQs3tXHgx6230xix0dWkuiTmwg+TFY8xfNdjptIkFuyODCcjAjIQDqOx98lpGvwQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.1.1.tgz", + "integrity": "sha512-h9eVJTt5WXnDyKUPiJ6Qn8lhhagPd1biaUbPrCWJb4UF5VnIUf2eJwXPkDgRzc8fIKX26Qw9k+YJiFjmnqSOyQ==", "license": "Apache-2.0", "dependencies": { "@emotion/css": "^11.1.3", @@ -5628,22 +5629,22 @@ } }, "node_modules/@leafygreen-ui/hooks": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-8.3.4.tgz", - "integrity": "sha512-Qbhn4RRCQo/t5BkfCkN4uCYtKJlysI1yrLuaeftf6Mj3oB92e7pPJibzWCVwy5WwNK3vAqB9YV5IM+zDzjJZJQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-8.4.1.tgz", + "integrity": "sha512-WZ1p+HeYqqbWVDGTffkRLDE83K/GbjRDYW8jcSYgznba0NAkOkWT9n/+MJp83rd55iyPhBopOKx7270s/sIH4A==", "license": "Apache-2.0", "dependencies": { - "@leafygreen-ui/lib": "^14.0.2", + "@leafygreen-ui/lib": "^14.2.0", "lodash": "^4.17.21" } }, "node_modules/@leafygreen-ui/icon": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-13.1.2.tgz", - "integrity": "sha512-/TM731pMS8sVRfF8ciAkdZwCKExPNHJ3aDplhLRlnVuC66x+zDbxaiyAlkztFEGBtKeTug4TAMSVaOPeR54ZWg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-13.3.0.tgz", + "integrity": "sha512-//vun0KJrtMAN6pTCmQGT3brTFEpSE2LbNnwlJ+l8klG6bwEmcPF9xPCS+XU98/b3UhMhtXHpwbzYN29UteAYg==", "license": "Apache-2.0", "dependencies": { - "@leafygreen-ui/emotion": "^4.0.9", + "@leafygreen-ui/emotion": "^4.1.1", "lodash": "^4.17.21" } }, @@ -5718,20 +5719,20 @@ } }, "node_modules/@leafygreen-ui/leafygreen-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-4.0.2.tgz", - "integrity": "sha512-dZ9wYv8Sj/8yTDOvx6q6XQZLzDsz5eWoUlDTfGxwMS+hyIrReiPkqZdE87sBVongcKVwD8OBlPdr+XGeoeaLBw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-4.0.7.tgz", + "integrity": "sha512-By2Ov+V/YP+pmNn9DwaayXCP31oq7NKO/CwEoqyjE1j58S2Ti6u1Eacywt2a18pIjKPbnIqralX8Bhmd6BAl5Q==", "license": "Apache-2.0", "dependencies": { - "@leafygreen-ui/hooks": "^8.3.4", - "@leafygreen-ui/lib": "^14.0.2", + "@leafygreen-ui/hooks": "^8.4.1", + "@leafygreen-ui/lib": "^14.2.0", "react-transition-group": "^4.4.5" } }, "node_modules/@leafygreen-ui/lib": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-14.0.2.tgz", - "integrity": "sha512-ZmyBeY1wj6cg9Tam0TaH6LejJKx+5uobeyCtwwdoNdMdqXOxcPOYWmUKLTjXh3P/TXVjXgYIpLiANUHN7OtMbw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-14.2.0.tgz", + "integrity": "sha512-JWHFwtWXY52YL1uNFpHWvRUWVl5tkXQzyq2uEMFHyZQKYUG0of9o5V+Zc6vAXdMvvAhE3DeYvDjTpaQbUk1PrQ==", "license": "Apache-2.0", "dependencies": { "lodash": "^4.17.21" @@ -5819,9 +5820,9 @@ } }, "node_modules/@leafygreen-ui/palette": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-4.1.3.tgz", - "integrity": "sha512-lg76FxP4ThMiDCzhJwdENcvtyQeLE/T+tExmkclXnuXME4mmo4AVoH+iKScHiztDGgQrF4Hg2rGLUJf5IiR2Iw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-4.1.4.tgz", + "integrity": "sha512-pkNeNzlstEM7ceoLG1mG7PUGunEdUYemKjzVOcaCbNEoyZvX3Lf0KWb8tDmAEloSPdXxMOlO8hoef9JGNdEIcw==", "license": "Apache-2.0" }, "node_modules/@leafygreen-ui/pipeline": { @@ -5844,12 +5845,12 @@ } }, "node_modules/@leafygreen-ui/polymorphic": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-2.0.5.tgz", - "integrity": "sha512-ajtFuGgydvsKjyft+4FvCptzgoMK+QKcbWs21zLidMZSuQjTq1pt3IC01lFuog05+vVXdvZwHSkEbPzQ4qp3/A==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-2.0.9.tgz", + "integrity": "sha512-oeAzARBPXZkZeStTuPdXDKdfyBlmkK5AiJUeehwbI5p6uTidH1GPGti+y1sDtxUPkavwEmGlPL304QoXXeHB6Q==", "license": "Apache-2.0", "dependencies": { - "@leafygreen-ui/lib": "^14.0.2", + "@leafygreen-ui/lib": "^14.2.0", "lodash": "^4.17.21" } }, @@ -6006,6 +6007,25 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/@leafygreen-ui/skeleton-loader": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/skeleton-loader/-/skeleton-loader-2.0.11.tgz", + "integrity": "sha512-QDG5ppaMGT4cXnOEtmFfFYC3jNljIvHg9+5+tZas3gcbt6W6BV9v7OdyFX0ZsU5F+kDed5d2SsgipMp4bCgjJw==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/card": "^12.0.9", + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/icon": "^13.3.0", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", + "@leafygreen-ui/tokens": "^2.12.2", + "@leafygreen-ui/typography": "^20.1.8", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@leafygreen-ui/leafygreen-provider": "^4.0.7" + } + }, "node_modules/@leafygreen-ui/split-button": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@leafygreen-ui/split-button/-/split-button-4.1.5.tgz", @@ -6147,13 +6167,14 @@ } }, "node_modules/@leafygreen-ui/tokens": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-2.11.3.tgz", - "integrity": "sha512-IQJ0uJQldPNzedOCsoPKtwWsKPP9vtD2cCx/DC9HtLmdQbiuWXdcAw67psOjTBWCXYxRJo2Pd8RZ2LnAIrywug==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-2.12.2.tgz", + "integrity": "sha512-eVHQOk7lExNjGPVpLv2sGMUmAH0ZIpmu86NHe4n3RzHNQ2ziJUnw1CN94N6Y09qv00LvrZ2I05kbxLfX+kktvw==", "license": "Apache-2.0", "dependencies": { - "@leafygreen-ui/lib": "^14.0.2", - "@leafygreen-ui/palette": "^4.1.3", + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", "polished": "^4.2.2" } }, @@ -6179,20 +6200,20 @@ } }, "node_modules/@leafygreen-ui/typography": { - "version": "20.1.1", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-20.1.1.tgz", - "integrity": "sha512-kdGZaCfdb/UcvKg0yl0YAOYEw0DAY1O5ZCez88vpzxx64KlpivwaM++25Tu414jZFyL8TYG8OqobgH7LWVRxQQ==", + "version": "20.1.8", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-20.1.8.tgz", + "integrity": "sha512-roZz/Dopv/5A2TAvc5ysvi+s+R9SXkvPeIfJEcz9s9ZTi4RO5y7B8D81w3px0h1Fj8WnZDl8+bEPms4oak0EUw==", "license": "Apache-2.0", "dependencies": { - "@leafygreen-ui/emotion": "^4.0.9", - "@leafygreen-ui/icon": "^13.1.2", - "@leafygreen-ui/lib": "^14.0.2", - "@leafygreen-ui/palette": "^4.1.3", - "@leafygreen-ui/polymorphic": "^2.0.5", - "@leafygreen-ui/tokens": "^2.11.3" + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/icon": "^13.3.0", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", + "@leafygreen-ui/polymorphic": "^2.0.9", + "@leafygreen-ui/tokens": "^2.12.2" }, "peerDependencies": { - "@leafygreen-ui/leafygreen-provider": "^4.0.2" + "@leafygreen-ui/leafygreen-provider": "^4.0.7" } }, "node_modules/@leichtgewicht/base64-codec": { @@ -53993,15 +54014,15 @@ } }, "@leafygreen-ui/card": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/card/-/card-12.0.2.tgz", - "integrity": "sha512-4ffWqzDG3u4APPH4d8nWfdrE7xXedGOvbIAJK2JGhsZcSTLaUpxOF8MYTgYDzRLnRye3bhLA/Om7AUY28aGsMA==", - "requires": { - "@leafygreen-ui/emotion": "^4.0.9", - "@leafygreen-ui/lib": "^14.0.2", - "@leafygreen-ui/palette": "^4.1.3", - "@leafygreen-ui/polymorphic": "^2.0.5", - "@leafygreen-ui/tokens": "^2.11.3", + "version": "12.0.9", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/card/-/card-12.0.9.tgz", + "integrity": "sha512-Go3ys8cimZQ8yStVt/++/jiK4O/r5cRqYXyM8ZAEH16nVTgMaumtSjiEiCuF1jWO2WaNCf9fNU4A8WOi7WZvUw==", + "requires": { + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", + "@leafygreen-ui/polymorphic": "^2.0.9", + "@leafygreen-ui/tokens": "^2.12.2", "polished": "^4.2.2" } }, @@ -54110,9 +54131,9 @@ } }, "@leafygreen-ui/emotion": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.0.9.tgz", - "integrity": "sha512-f9ffU9FbIRi5KeOGO1YfAScQs3tXHgx6230xix0dWkuiTmwg+TFY8xfNdjptIkFuyODCcjAjIQDqOx98lpGvwQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.1.1.tgz", + "integrity": "sha512-h9eVJTt5WXnDyKUPiJ6Qn8lhhagPd1biaUbPrCWJb4UF5VnIUf2eJwXPkDgRzc8fIKX26Qw9k+YJiFjmnqSOyQ==", "requires": { "@emotion/css": "^11.1.3", "@emotion/server": "^11.4.0" @@ -54154,20 +54175,20 @@ } }, "@leafygreen-ui/hooks": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-8.3.4.tgz", - "integrity": "sha512-Qbhn4RRCQo/t5BkfCkN4uCYtKJlysI1yrLuaeftf6Mj3oB92e7pPJibzWCVwy5WwNK3vAqB9YV5IM+zDzjJZJQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-8.4.1.tgz", + "integrity": "sha512-WZ1p+HeYqqbWVDGTffkRLDE83K/GbjRDYW8jcSYgznba0NAkOkWT9n/+MJp83rd55iyPhBopOKx7270s/sIH4A==", "requires": { - "@leafygreen-ui/lib": "^14.0.2", + "@leafygreen-ui/lib": "^14.2.0", "lodash": "^4.17.21" } }, "@leafygreen-ui/icon": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-13.1.2.tgz", - "integrity": "sha512-/TM731pMS8sVRfF8ciAkdZwCKExPNHJ3aDplhLRlnVuC66x+zDbxaiyAlkztFEGBtKeTug4TAMSVaOPeR54ZWg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-13.3.0.tgz", + "integrity": "sha512-//vun0KJrtMAN6pTCmQGT3brTFEpSE2LbNnwlJ+l8klG6bwEmcPF9xPCS+XU98/b3UhMhtXHpwbzYN29UteAYg==", "requires": { - "@leafygreen-ui/emotion": "^4.0.9", + "@leafygreen-ui/emotion": "^4.1.1", "lodash": "^4.17.21" } }, @@ -54226,19 +54247,19 @@ } }, "@leafygreen-ui/leafygreen-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-4.0.2.tgz", - "integrity": "sha512-dZ9wYv8Sj/8yTDOvx6q6XQZLzDsz5eWoUlDTfGxwMS+hyIrReiPkqZdE87sBVongcKVwD8OBlPdr+XGeoeaLBw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-4.0.7.tgz", + "integrity": "sha512-By2Ov+V/YP+pmNn9DwaayXCP31oq7NKO/CwEoqyjE1j58S2Ti6u1Eacywt2a18pIjKPbnIqralX8Bhmd6BAl5Q==", "requires": { - "@leafygreen-ui/hooks": "^8.3.4", - "@leafygreen-ui/lib": "^14.0.2", + "@leafygreen-ui/hooks": "^8.4.1", + "@leafygreen-ui/lib": "^14.2.0", "react-transition-group": "^4.4.5" } }, "@leafygreen-ui/lib": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-14.0.2.tgz", - "integrity": "sha512-ZmyBeY1wj6cg9Tam0TaH6LejJKx+5uobeyCtwwdoNdMdqXOxcPOYWmUKLTjXh3P/TXVjXgYIpLiANUHN7OtMbw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-14.2.0.tgz", + "integrity": "sha512-JWHFwtWXY52YL1uNFpHWvRUWVl5tkXQzyq2uEMFHyZQKYUG0of9o5V+Zc6vAXdMvvAhE3DeYvDjTpaQbUk1PrQ==", "requires": { "lodash": "^4.17.21" } @@ -54309,9 +54330,9 @@ } }, "@leafygreen-ui/palette": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-4.1.3.tgz", - "integrity": "sha512-lg76FxP4ThMiDCzhJwdENcvtyQeLE/T+tExmkclXnuXME4mmo4AVoH+iKScHiztDGgQrF4Hg2rGLUJf5IiR2Iw==" + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-4.1.4.tgz", + "integrity": "sha512-pkNeNzlstEM7ceoLG1mG7PUGunEdUYemKjzVOcaCbNEoyZvX3Lf0KWb8tDmAEloSPdXxMOlO8hoef9JGNdEIcw==" }, "@leafygreen-ui/pipeline": { "version": "7.0.2", @@ -54329,11 +54350,11 @@ } }, "@leafygreen-ui/polymorphic": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-2.0.5.tgz", - "integrity": "sha512-ajtFuGgydvsKjyft+4FvCptzgoMK+QKcbWs21zLidMZSuQjTq1pt3IC01lFuog05+vVXdvZwHSkEbPzQ4qp3/A==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-2.0.9.tgz", + "integrity": "sha512-oeAzARBPXZkZeStTuPdXDKdfyBlmkK5AiJUeehwbI5p6uTidH1GPGti+y1sDtxUPkavwEmGlPL304QoXXeHB6Q==", "requires": { - "@leafygreen-ui/lib": "^14.0.2", + "@leafygreen-ui/lib": "^14.2.0", "lodash": "^4.17.21" } }, @@ -54462,6 +54483,21 @@ } } }, + "@leafygreen-ui/skeleton-loader": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/skeleton-loader/-/skeleton-loader-2.0.11.tgz", + "integrity": "sha512-QDG5ppaMGT4cXnOEtmFfFYC3jNljIvHg9+5+tZas3gcbt6W6BV9v7OdyFX0ZsU5F+kDed5d2SsgipMp4bCgjJw==", + "requires": { + "@leafygreen-ui/card": "^12.0.9", + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/icon": "^13.3.0", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", + "@leafygreen-ui/tokens": "^2.12.2", + "@leafygreen-ui/typography": "^20.1.8", + "lodash": "^4.17.21" + } + }, "@leafygreen-ui/split-button": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@leafygreen-ui/split-button/-/split-button-4.1.5.tgz", @@ -54577,12 +54613,13 @@ } }, "@leafygreen-ui/tokens": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-2.11.3.tgz", - "integrity": "sha512-IQJ0uJQldPNzedOCsoPKtwWsKPP9vtD2cCx/DC9HtLmdQbiuWXdcAw67psOjTBWCXYxRJo2Pd8RZ2LnAIrywug==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-2.12.2.tgz", + "integrity": "sha512-eVHQOk7lExNjGPVpLv2sGMUmAH0ZIpmu86NHe4n3RzHNQ2ziJUnw1CN94N6Y09qv00LvrZ2I05kbxLfX+kktvw==", "requires": { - "@leafygreen-ui/lib": "^14.0.2", - "@leafygreen-ui/palette": "^4.1.3", + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", "polished": "^4.2.2" } }, @@ -54604,16 +54641,16 @@ } }, "@leafygreen-ui/typography": { - "version": "20.1.1", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-20.1.1.tgz", - "integrity": "sha512-kdGZaCfdb/UcvKg0yl0YAOYEw0DAY1O5ZCez88vpzxx64KlpivwaM++25Tu414jZFyL8TYG8OqobgH7LWVRxQQ==", - "requires": { - "@leafygreen-ui/emotion": "^4.0.9", - "@leafygreen-ui/icon": "^13.1.2", - "@leafygreen-ui/lib": "^14.0.2", - "@leafygreen-ui/palette": "^4.1.3", - "@leafygreen-ui/polymorphic": "^2.0.5", - "@leafygreen-ui/tokens": "^2.11.3" + "version": "20.1.8", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-20.1.8.tgz", + "integrity": "sha512-roZz/Dopv/5A2TAvc5ysvi+s+R9SXkvPeIfJEcz9s9ZTi4RO5y7B8D81w3px0h1Fj8WnZDl8+bEPms4oak0EUw==", + "requires": { + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/icon": "^13.3.0", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", + "@leafygreen-ui/polymorphic": "^2.0.9", + "@leafygreen-ui/tokens": "^2.12.2" } }, "@leichtgewicht/base64-codec": { diff --git a/package.json b/package.json index 9c25534765c..89a7f18f2ae 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ } }, "dependencies": { + "@leafygreen-ui/skeleton-loader": "^2.0.11", "mongodb-mql-engines": "^0.0.3" } } diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 194031fef0f..690fea17850 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -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'; diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx index 74b766da121..82001c0bf24 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx @@ -4,18 +4,17 @@ import { Body, cx, useFocusRing, + ParagraphSkeleton, } from '@mongodb-js/compass-components'; import React, { useMemo, useCallback } from 'react'; -import { EJSON } from 'bson'; import { css, spacing } from '@mongodb-js/compass-components'; import { CodemirrorMultilineEditor, createQueryAutocompleter, } from '@mongodb-js/compass-editor'; import MDBCodeViewer from './mdb-code-viewer'; -import * as mql from 'mongodb-mql-engines'; import type { RootState } from '../../modules'; -import { suggestedIndexFetched } from '../../modules/create-index'; +import { fetchIndexSuggestions } from '../../modules/create-index'; import type { IndexSuggestionState, SuggestedIndexFetchedProps, @@ -23,7 +22,6 @@ import type { import { connect } from 'react-redux'; const inputQueryContainerStyles = css({ - marginBottom: spacing[600], display: 'flex', flexDirection: 'column', }); @@ -69,6 +67,14 @@ 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, @@ -76,17 +82,19 @@ const QueryFlowSection = ({ collectionName, onSuggestedIndexButtonClick, indexSuggestions, + fetchingSuggestionsState, }: { schemaFields: { name: string; description?: string }[]; serverVersion: string; dbName: string; collectionName: string; onSuggestedIndexButtonClick: ({ - indexSuggestions, - error, - indexSuggestionsState, - }: SuggestedIndexFetchedProps) => void; + dbName, + collectionName, + inputQuery, + }: SuggestedIndexFetchedProps) => Promise; indexSuggestions: Record | null; + fetchingSuggestionsState: IndexSuggestionState; }) => { const [inputQuery, setInputQuery] = React.useState(''); const completer = useMemo( @@ -106,41 +114,17 @@ const QueryFlowSection = ({ }); const handleSuggestedIndexButtonClick = useCallback(async () => { - try { - const sanitizedInputQuery = inputQuery.trim(); - const namespace = mql.analyzeNamespace( - { database: dbName, collection: collectionName }, - [] - ); - const query = mql.parseQuery( - EJSON.parse(sanitizedInputQuery, { relaxed: false }), - namespace - ); - const results = await mql.suggestIndex([query]); - - if (results?.index) { - onSuggestedIndexButtonClick({ - indexSuggestions: results.index, - error: null, - indexSuggestionsState: 'success', - }); - } else { - onSuggestedIndexButtonClick({ - indexSuggestions: null, - error: - 'No suggested index found. Please choose Start with an Index at the top to continue.', - indexSuggestionsState: 'error', - }); - } - } catch (error) { - onSuggestedIndexButtonClick({ - indexSuggestions: null, - error: 'Error parsing query. Please follow query structure.', - indexSuggestionsState: 'error', - }); - } + const sanitizedInputQuery = inputQuery.trim(); + + await onSuggestedIndexButtonClick({ + dbName, + collectionName, + inputQuery: sanitizedInputQuery, + }); }, [inputQuery, dbName, collectionName, onSuggestedIndexButtonClick]); + const isFetchingIndexSuggestions = fetchingSuggestionsState === 'fetching'; + return ( <> @@ -179,35 +163,45 @@ const QueryFlowSection = ({ - {indexSuggestions && ( - <> - - Suggested Index - {' '} -
- {/* TODO in CLOUDP-311786, replace hardcoded values with actual data */} - -
- + + {(isFetchingIndexSuggestions || indexSuggestions) && ( + + Suggested Index + + )} + + {isFetchingIndexSuggestions ? ( + + ) : ( + indexSuggestions && ( + <> +
+ +
+ + ) )} ); }; const mapState = ({ createIndex }: RootState) => { - const { indexSuggestions } = createIndex; + const { indexSuggestions, sampleDocs, fetchingSuggestionsState } = + createIndex; return { indexSuggestions, + sampleDocs, + fetchingSuggestionsState, }; }; const mapDispatch = { - onSuggestedIndexButtonClick: suggestedIndexFetched, + onSuggestedIndexButtonClick: fetchIndexSuggestions, }; export default connect(mapState, mapDispatch)(QueryFlowSection); diff --git a/packages/compass-indexes/src/modules/create-index.tsx b/packages/compass-indexes/src/modules/create-index.tsx index 8aeb2c4b8c0..da82454f1d0 100644 --- a/packages/compass-indexes/src/modules/create-index.tsx +++ b/packages/compass-indexes/src/modules/create-index.tsx @@ -1,3 +1,4 @@ +import type { Document } from 'mongodb'; import { EJSON, ObjectId } from 'bson'; import type { CreateIndexesOptions, IndexDirection } from 'mongodb'; import { isCollationValid } from 'mongodb-query-parser'; @@ -8,6 +9,7 @@ import { isAction } from '../utils/is-action'; import type { IndexesThunkAction } from '.'; import type { RootState } from '.'; import { createRegularIndex } from './regular-indexes'; +import * as mql from 'mongodb-mql-engines'; export enum ActionTypes { FieldAdded = 'compass-indexes/create-index/fields/field-added', @@ -300,9 +302,17 @@ export type State = { // current tab that user is on (Query Flow or Index Flow) currentTab: Tab; + // state of the index suggestions fetchingSuggestionsState: IndexSuggestionState; + + // error specific to fetching index suggestions fetchingSuggestionsError: Error | string | null; + + // index suggestions in a format such as {fieldName: 1} indexSuggestions: Record | null; + + // sample documents used for getting index suggestions + sampleDocs: Array; }; export const INITIAL_STATE: State = { @@ -315,6 +325,7 @@ export const INITIAL_STATE: State = { fetchingSuggestionsState: 'initial', fetchingSuggestionsError: null, indexSuggestions: null, + sampleDocs: [], }; function getInitialState(): State { @@ -351,54 +362,93 @@ type SuggestedIndexesRequestedAction = { type: ActionTypes.SuggestedIndexesRequested; }; -export const suggestedIndexesRequested = - (): SuggestedIndexesRequestedAction => ({ - type: ActionTypes.SuggestedIndexesRequested, - }); - export type SuggestedIndexFetchedAction = { type: ActionTypes.SuggestedIndexesFetched; sampleDocs: Array; indexSuggestions: { [key: string]: number } | null; - error: string | null; + fetchingSuggestionsError: string | null; indexSuggestionsState: IndexSuggestionState; }; export type SuggestedIndexFetchedProps = { - indexSuggestions: { [key: string]: number } | null; - error: string | null; - indexSuggestionsState: IndexSuggestionState; + dbName: string; + collectionName: string; + inputQuery: string; }; -// TODO: dispatch sample docs in here -export const suggestedIndexFetched = ({ - indexSuggestions, - error, - indexSuggestionsState, +export const fetchIndexSuggestions = ({ + dbName, + collectionName, + inputQuery, }: { - indexSuggestions: { [key: string]: number } | null; - error: string | null; - indexSuggestionsState: IndexSuggestionState; + dbName: string; + collectionName: string; + inputQuery: string; }): IndexesThunkAction< - void, - SuggestedIndexFetchedAction | ErrorEncounteredAction + Promise, + SuggestedIndexFetchedAction | SuggestedIndexesRequestedAction > => { - return (dispatch, getState, { track, preferences }) => { - if (error) { - dispatch(errorEncountered(error)); - return; - } - + return async (dispatch, getState, { dataService }) => { dispatch({ - type: ActionTypes.SuggestedIndexesFetched, - sampleDocs: [], - indexSuggestions, - error, - indexSuggestionsState, + type: ActionTypes.SuggestedIndexesRequested, }); + const namespace = `${dbName}.${collectionName}`; + let sampleDocuments: Array = + getState().createIndex.sampleDocs || []; + + // Get sample documents from state if it's already there, otherwise fetch it + if (!sampleDocuments || sampleDocuments.length === 0) { + try { + sampleDocuments = + (await dataService.sample(namespace, { size: 50 })) || []; + } catch (e) { + // Swallow the error because mql package still will work fine with empty sampleDocuments + } + } - if (error) { - dispatch(errorEncountered(error)); + // Analyze namespace and fetch suggestions + try { + const analyzedNamespace = mql.analyzeNamespace( + { database: dbName, collection: collectionName }, + sampleDocuments + ); + + const query = mql.parseQuery( + EJSON.parse(inputQuery, { relaxed: false }), + analyzedNamespace + ); + const results = await mql.suggestIndex([query]); + const indexSuggestions = results?.index || null; + + // TODO in CLOUDP-311787: add info banner and update the current error banner to take in fetchingSuggestionsError as well + if (!indexSuggestions) { + dispatch({ + type: ActionTypes.SuggestedIndexesFetched, + sampleDocs: sampleDocuments, + indexSuggestions, + fetchingSuggestionsError: + 'No suggested index found. Please choose Start with an Index at the top to continue.', + indexSuggestionsState: 'error', + }); + return; + } + + dispatch({ + type: ActionTypes.SuggestedIndexesFetched, + sampleDocs: sampleDocuments, + indexSuggestions, + fetchingSuggestionsError: null, + indexSuggestionsState: 'success', + }); + } catch (e) { + dispatch({ + type: ActionTypes.SuggestedIndexesFetched, + sampleDocs: sampleDocuments, + indexSuggestions: null, + fetchingSuggestionsError: + 'Error parsing query. Please follow query structure.', + indexSuggestionsState: 'error', + }); } }; }; @@ -718,7 +768,7 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { return { ...state, fetchingSuggestionsState: action.indexSuggestionsState, - fetchingSuggestionsError: action.error, + fetchingSuggestionsError: action.fetchingSuggestionsError, indexSuggestions: action.indexSuggestions, }; } diff --git a/packages/compass-indexes/src/stores/store.ts b/packages/compass-indexes/src/stores/store.ts index ad9cde454e7..61db381a116 100644 --- a/packages/compass-indexes/src/stores/store.ts +++ b/packages/compass-indexes/src/stores/store.ts @@ -44,6 +44,7 @@ export type IndexesDataServiceProps = | 'createSearchIndex' | 'updateSearchIndex' | 'dropSearchIndex' + | 'sample' // Required for collection model (fetching stats) | 'collectionStats' | 'collectionInfo' From 696ea63ca77737e925a13f9510e8254aafd9e0d9 Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Thu, 1 May 2025 13:08:36 -0400 Subject: [PATCH 04/13] fixed sample docs fetching bug --- .../compass-indexes/src/modules/create-index.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/compass-indexes/src/modules/create-index.tsx b/packages/compass-indexes/src/modules/create-index.tsx index da82454f1d0..0bf7d3e4e61 100644 --- a/packages/compass-indexes/src/modules/create-index.tsx +++ b/packages/compass-indexes/src/modules/create-index.tsx @@ -312,7 +312,7 @@ export type State = { indexSuggestions: Record | null; // sample documents used for getting index suggestions - sampleDocs: Array; + sampleDocs: Array | null; }; export const INITIAL_STATE: State = { @@ -325,7 +325,7 @@ export const INITIAL_STATE: State = { fetchingSuggestionsState: 'initial', fetchingSuggestionsError: null, indexSuggestions: null, - sampleDocs: [], + sampleDocs: null, }; function getInitialState(): State { @@ -393,16 +393,19 @@ export const fetchIndexSuggestions = ({ type: ActionTypes.SuggestedIndexesRequested, }); const namespace = `${dbName}.${collectionName}`; - let sampleDocuments: Array = - getState().createIndex.sampleDocs || []; // Get sample documents from state if it's already there, otherwise fetch it - if (!sampleDocuments || sampleDocuments.length === 0) { + let sampleDocuments: Array | null = + getState().createIndex.sampleDocs || null; + + // If it's null, that means it has not been fetched before + if (sampleDocuments === null) { try { sampleDocuments = (await dataService.sample(namespace, { size: 50 })) || []; } catch (e) { // Swallow the error because mql package still will work fine with empty sampleDocuments + sampleDocuments = []; } } @@ -770,6 +773,7 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { fetchingSuggestionsState: action.indexSuggestionsState, fetchingSuggestionsError: action.fetchingSuggestionsError, indexSuggestions: action.indexSuggestions, + sampleDocs: action.sampleDocs, }; } From a01e3acd5da566031a631a2f367c9ed98ab284da Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Fri, 2 May 2025 12:06:12 -0400 Subject: [PATCH 05/13] update mql versino --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 525665d5b97..c64b63d86e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ ], "dependencies": { "@leafygreen-ui/skeleton-loader": "^2.0.11", - "mongodb-mql-engines": "^0.0.3" + "mongodb-mql-engines": "^0.0.4" }, "devDependencies": { "@mongodb-js/monorepo-tools": "^1.1.16", @@ -31668,9 +31668,9 @@ } }, "node_modules/mongodb-mql-engines": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mongodb-mql-engines/-/mongodb-mql-engines-0.0.3.tgz", - "integrity": "sha512-lJXvdYw5Xozwguboyr3PzVkK9EyQdVnzD3qA3i8OY8yi1QZ7YOm7EKhoMnqUV76ZuMRWLjHSZwwzAfEDhKn2+g==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mongodb-mql-engines/-/mongodb-mql-engines-0.0.4.tgz", + "integrity": "sha512-QPOSUnp4eeR4vVcF68VTsx1jutZbmNJYVFpbQ8loPPrynLJlxrtLr1PWV6UMunkHVLGMDJuGcsWQ/7JI5RzOcA==", "license": "Apache-2.0", "dependencies": { "format-util": "^1.0.5" @@ -80200,9 +80200,9 @@ } }, "mongodb-mql-engines": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mongodb-mql-engines/-/mongodb-mql-engines-0.0.3.tgz", - "integrity": "sha512-lJXvdYw5Xozwguboyr3PzVkK9EyQdVnzD3qA3i8OY8yi1QZ7YOm7EKhoMnqUV76ZuMRWLjHSZwwzAfEDhKn2+g==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mongodb-mql-engines/-/mongodb-mql-engines-0.0.4.tgz", + "integrity": "sha512-QPOSUnp4eeR4vVcF68VTsx1jutZbmNJYVFpbQ8loPPrynLJlxrtLr1PWV6UMunkHVLGMDJuGcsWQ/7JI5RzOcA==", "requires": { "format-util": "^1.0.5" } diff --git a/package.json b/package.json index 89a7f18f2ae..dbd1060745c 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,6 @@ }, "dependencies": { "@leafygreen-ui/skeleton-loader": "^2.0.11", - "mongodb-mql-engines": "^0.0.3" + "mongodb-mql-engines": "^0.0.4" } } From dad3336d8247cceee80e347869df14049e5fbd7a Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Mon, 5 May 2025 10:28:19 -0400 Subject: [PATCH 06/13] added testing for the three states --- .../query-flow-section.spec.tsx | 87 ++++++++++++++----- .../create-index-form/query-flow-section.tsx | 5 +- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx index 5dddd31366e..fce680fb71c 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx @@ -2,35 +2,82 @@ 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', () => { + let store; const renderComponent = () => { + store = setupStore(); + render( - + + + ); }; - 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 the suggested index section with formatted index code', () => { + 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(async () => { + renderComponent(); + + await 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; + + // TODO: create tests to see that db name, collection name, and queries show up + }); }); }); diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx index 82001c0bf24..bb48e8a915c 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx @@ -171,7 +171,10 @@ const QueryFlowSection = ({ )} {isFetchingIndexSuggestions ? ( - + ) : ( indexSuggestions && ( <> From fc14a5b4357b19b664e17e6aef847b9cc5ec9806 Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Mon, 5 May 2025 11:36:39 -0400 Subject: [PATCH 07/13] finished adding test for success state --- .../create-index-form/query-flow-section.spec.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx index fce680fb71c..c3526444a05 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx @@ -8,16 +8,18 @@ import { ActionTypes } from '../../modules/create-index'; describe('QueryFlowSection', () => { let store; + const dbName = 'fakeDBName'; + const collectionName = 'fakeCollectionName'; const renderComponent = () => { - store = setupStore(); + const store = setupStore(); render( ); @@ -52,7 +54,7 @@ describe('QueryFlowSection', () => { type: ActionTypes.SuggestedIndexesRequested, }); }); - it('renders the suggested index section with formatted index code', () => { + it('renders a loader for the code section', () => { const loader = screen.getByTestId('query-flow-section-code-loader'); expect(loader).to.be.visible; }); @@ -76,8 +78,9 @@ describe('QueryFlowSection', () => { 'query-flow-section-suggested-index' ); expect(codeElement).to.be.visible; - - // TODO: create tests to see that db name, collection name, and queries show up + expect(codeElement).to.have.text( + `db.getSiblingDB("${dbName}").getCollection("${collectionName}").createIndex({ "a": 1, "b": "2"});` + ); }); }); }); From 9a56398c87097c87ebfac30a45e66ba8dc43c8cd Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Mon, 5 May 2025 12:48:42 -0400 Subject: [PATCH 08/13] address pr comments --- package-lock.json | 8 ++++---- package.json | 4 ---- packages/compass-components/package.json | 1 + packages/compass-indexes/package.json | 3 ++- .../components/create-index-form/query-flow-section.tsx | 8 +++----- packages/compass-indexes/src/modules/create-index.tsx | 5 +++-- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index c64b63d86e9..ac4fca55531 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,6 @@ "configs/*", "scripts" ], - "dependencies": { - "@leafygreen-ui/skeleton-loader": "^2.0.11", - "mongodb-mql-engines": "^0.0.4" - }, "devDependencies": { "@mongodb-js/monorepo-tools": "^1.1.16", "@mongodb-js/sbom-tools": "^0.7.2", @@ -43795,6 +43791,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", @@ -45640,6 +45637,7 @@ "mongodb": "^6.14.1", "mongodb-collection-model": "^5.25.8", "mongodb-data-service": "^22.25.8", + "mongodb-mql-engines": "^0.0.4", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "numeral": "^2.0.6", @@ -56271,6 +56269,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", @@ -57500,6 +57499,7 @@ "mongodb": "^6.14.1", "mongodb-collection-model": "^5.25.8", "mongodb-data-service": "^22.25.8", + "mongodb-mql-engines": "^0.0.4", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "numeral": "^2.0.6", diff --git a/package.json b/package.json index dbd1060745c..d25b01690f9 100644 --- a/package.json +++ b/package.json @@ -103,9 +103,5 @@ "chai-enzyme": { "cheerio": "1.0.0-rc.10" } - }, - "dependencies": { - "@leafygreen-ui/skeleton-loader": "^2.0.11", - "mongodb-mql-engines": "^0.0.4" } } diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index d44c86a981b..4b23d5cebf7 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -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", diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 0f795aeab40..4eddd767459 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -91,7 +91,8 @@ "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2", - "semver": "^7.6.2" + "semver": "^7.6.2", + "mongodb-mql-engines": "^0.0.4" }, "is_compass_plugin": true } diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx index bb48e8a915c..b9b973c3017 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx @@ -113,10 +113,10 @@ const QueryFlowSection = ({ radius: editorContainerRadius, }); - const handleSuggestedIndexButtonClick = useCallback(async () => { + const handleSuggestedIndexButtonClick = useCallback(() => { const sanitizedInputQuery = inputQuery.trim(); - await onSuggestedIndexButtonClick({ + void onSuggestedIndexButtonClick({ dbName, collectionName, inputQuery: sanitizedInputQuery, @@ -153,9 +153,7 @@ const QueryFlowSection = ({