diff --git a/package-lock.json b/package-lock.json index b33039dc29a..f791aee72fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13105,21 +13105,21 @@ } }, "node_modules/@testing-library/react": { - "version": "12.1.4", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.4.tgz", - "integrity": "sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.0.0", - "@types/react-dom": "*" + "@types/react-dom": "<18.0.0" }, "engines": { "node": ">=12" }, "peerDependencies": { - "react": "*", - "react-dom": "*" + "react": "<18.0.0", + "react-dom": "<18.0.0" } }, "node_modules/@testing-library/react-hooks": { @@ -44232,6 +44232,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-utils": "^0.5.9", @@ -44297,6 +44298,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-utils": "^0.5.9", @@ -44858,6 +44860,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-query-bar": "^8.24.1", "@mongodb-js/compass-workspaces": "^0.4.1", @@ -44911,6 +44914,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-query-bar": "^8.24.1", "@mongodb-js/compass-workspaces": "^0.4.1", @@ -46113,6 +46117,8 @@ "@mongodb-js/prettier-config-compass": "^1.0.1", "@mongodb-js/tsconfig-compass": "^1.0.3", "@mongodb-js/webpack-config-compass": "^1.3.1", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -46125,12 +46131,15 @@ "mongodb-schema": "^12.1.0", "nyc": "^15.1.0", "prettier": "^2.7.1", + "react": "^17.0.2", + "react-redux": "^8.1.3", "redux": "^4.2.1", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" }, "peerDependencies": { - "hadron-app-registry": "^9.1.4" + "hadron-app-registry": "^9.1.4", + "react": "^17.0.2" } }, "packages/compass-field-store/node_modules/diff": { @@ -46641,6 +46650,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-workspaces": "^0.4.1", "@mongodb-js/mongodb-constants": "^0.8.7", @@ -46683,6 +46693,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-workspaces": "^0.4.1", "@mongodb-js/mongodb-constants": "^0.8.7", @@ -47019,6 +47030,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/mongodb-constants": "^0.8.7", @@ -47064,6 +47076,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/mongodb-constants": "^0.8.7", @@ -47272,6 +47285,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "bson": "^6.2.0", "compass-preferences-model": "^2.17.1", @@ -47312,6 +47326,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "bson": "^6.2.0", "compass-preferences-model": "^2.17.1", @@ -58632,6 +58647,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-utils": "^0.5.9", @@ -59126,6 +59142,7 @@ "@mongodb-js/compass-collection": "^4.22.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-query-bar": "^8.24.1", "@mongodb-js/compass-test-server": "^0.1.10", @@ -59497,6 +59514,8 @@ "@mongodb-js/prettier-config-compass": "^1.0.1", "@mongodb-js/tsconfig-compass": "^1.0.3", "@mongodb-js/webpack-config-compass": "^1.3.1", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -59510,6 +59529,8 @@ "mongodb-schema": "^12.1.0", "nyc": "^15.1.0", "prettier": "^2.7.1", + "react": "^17.0.2", + "react-redux": "^8.1.3", "redux": "^4.2.1", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" @@ -59892,6 +59913,7 @@ "@mongodb-js/compass-collection": "^4.22.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-workspaces": "^0.4.1", "@mongodb-js/eslint-config-compass": "^1.0.14", @@ -60095,6 +60117,7 @@ "@mongodb-js/compass-collection": "^4.22.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/eslint-config-compass": "^1.0.14", @@ -60311,6 +60334,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/eslint-config-compass": "^1.0.14", "@mongodb-js/mocha-config-compass": "^1.3.5", @@ -67655,14 +67679,14 @@ } }, "@testing-library/react": { - "version": "12.1.4", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.4.tgz", - "integrity": "sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.0.0", - "@types/react-dom": "*" + "@types/react-dom": "<18.0.0" } }, "@testing-library/react-hooks": { diff --git a/packages/compass-aggregations/package.json b/packages/compass-aggregations/package.json index 5a76cbe4cd3..e0280402f4c 100644 --- a/packages/compass-aggregations/package.json +++ b/packages/compass-aggregations/package.json @@ -43,6 +43,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-utils": "^0.5.9", @@ -109,6 +110,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-utils": "^0.5.9", diff --git a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.spec.tsx b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.spec.tsx index bbcb12c8c26..d9bb32d7638 100644 --- a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.spec.tsx @@ -16,11 +16,11 @@ const renderPipelineEditor = ( render( {}} num_stages={1} {...props} diff --git a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.tsx b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.tsx index ed95f75f051..9fa4d8d8cd2 100644 --- a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.tsx +++ b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-as-text-workspace/pipeline-editor.tsx @@ -19,6 +19,7 @@ import type { MongoServerError } from 'mongodb'; import { changeEditorValue } from '../../../modules/pipeline-builder/text-editor-pipeline'; import type { PipelineParserError } from '../../../modules/pipeline-builder/pipeline-parser/utils'; import { useLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; +import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; const containerStyles = css({ position: 'relative', @@ -58,24 +59,25 @@ const errorContainerStyles = css({ }); export type PipelineEditorProps = { + namespace: string; num_stages: number; pipelineText: string; syntaxErrors: PipelineParserError[]; serverError: MongoServerError | null; serverVersion: string; - fields: { name: string }[]; onChangePipelineText: (value: string) => void; }; export const PipelineEditor: React.FunctionComponent = ({ + namespace, num_stages, pipelineText, serverError, syntaxErrors, serverVersion, - fields, onChangePipelineText, }) => { + const fields = useAutocompleteFields(namespace); const { track } = useLoggerAndTelemetry('COMPASS-AGGREGATIONS-UI'); const editorInitialValueRef = useRef(pipelineText); const editorCurrentValueRef = useRef(pipelineText); @@ -158,6 +160,7 @@ export const PipelineEditor: React.FunctionComponent = ({ }; const mapState = ({ + namespace, pipelineBuilder: { textEditor: { pipeline: { @@ -170,14 +173,13 @@ const mapState = ({ }, }, serverVersion, - fields, }: RootState) => ({ + namespace, num_stages: pipeline.length, pipelineText, serverError: pipelineServerError ?? outputStageServerError, syntaxErrors, serverVersion, - fields, }); const mapDispatch = { diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/index.spec.tsx index 06b64700ce7..02358ee5ff9 100644 --- a/packages/compass-aggregations/src/components/pipeline-results-workspace/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/index.spec.tsx @@ -15,6 +15,7 @@ const renderPipelineResultsWorkspace = ( render( = ({ + namespace, documents, isLoading, error, @@ -175,7 +177,11 @@ export const PipelineResultsWorkspace: React.FunctionComponent< results = ; } else { results = ( - + ); } @@ -195,6 +201,7 @@ const mapState = (state: RootState) => { const stageOperator = getStageOperator(lastStage) ?? ''; return { + namespace, documents, isLoading: loading, error, diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.spec.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.spec.tsx index 388ebcced23..ed8e52f9181 100644 --- a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.spec.tsx @@ -7,7 +7,13 @@ import PipelineResultsList from './pipeline-results-list'; describe('PipelineResultsList', function () { it('does not render when documents are empty', function () { - render(); + render( + + ); expect(() => { screen.getByTestId('document-list-item'); }).to.throw; @@ -16,6 +22,7 @@ describe('PipelineResultsList', function () { it('renders list view', function () { render( = ({ documents, view }) => { +}> = ({ namespace, documents, view }) => { const copyToClipboard = useCallback((doc: HadronDocument) => { const str = doc.toEJSON(); void navigator.clipboard.writeText(str); @@ -27,6 +28,7 @@ const PipelineResultsList: React.FunctionComponent<{ return ( (stageValue); editorCurrentValueRef.current = stageValue; + const fields = useAutocompleteFields(namespace); + const completer = useMemo(() => { return createStageAutocompleter({ serverVersion, stageOperator: stageOperator ?? undefined, - fields: autocompleteFields, + fields, }); - }, [autocompleteFields, serverVersion, stageOperator]); + }, [fields, serverVersion, stageOperator]); const annotations = useMemo(() => { if (syntaxError?.loc?.index) { @@ -195,15 +198,12 @@ export default connect( const stage = stages[ownProps.index] as StoreStage; const num_stages = pipelineFromStore(stages).length; return { + namespace: state.namespace, stageValue: stage.value, stageOperator: stage.stageOperator, syntaxError: !stage.empty ? stage.syntaxError ?? null : null, serverError: !stage.empty ? stage.serverError ?? null : null, serverVersion: state.serverVersion, - autocompleteFields: state.fields as { - name: string; - description?: string; - }[], num_stages, editor_view_type: mapPipelineModeToEditorViewType(state), }; diff --git a/packages/compass-aggregations/src/components/stage-wizard/index.spec.tsx b/packages/compass-aggregations/src/components/stage-wizard/index.spec.tsx index 890b94282e9..d698a7f6eb4 100644 --- a/packages/compass-aggregations/src/components/stage-wizard/index.spec.tsx +++ b/packages/compass-aggregations/src/components/stage-wizard/index.spec.tsx @@ -11,6 +11,7 @@ const renderStageWizard = ( ) => { return render( {}} onCancel={() => {}} onChange={() => {}} @@ -21,7 +22,7 @@ const renderStageWizard = ( setNodeRef={() => {}} style={{}} listeners={undefined} - fields={[]} + previousStageFields={[]} {...props} /> ); diff --git a/packages/compass-aggregations/src/components/stage-wizard/index.tsx b/packages/compass-aggregations/src/components/stage-wizard/index.tsx index 8e8061e776a..10508f798ce 100644 --- a/packages/compass-aggregations/src/components/stage-wizard/index.tsx +++ b/packages/compass-aggregations/src/components/stage-wizard/index.tsx @@ -15,11 +15,7 @@ import { Badge, WarningSummary, } from '@mongodb-js/compass-components'; - -import type { - StageWizardUseCase, - WizardComponentProps, -} from '../aggregation-side-panel/stage-wizard-use-cases'; +import type { StageWizardUseCase } from '../aggregation-side-panel/stage-wizard-use-cases'; import { STAGE_WIZARD_USE_CASES } from '../aggregation-side-panel/stage-wizard-use-cases'; import { connect } from 'react-redux'; import type { PipelineBuilderThunkDispatch, RootState } from '../../modules'; @@ -35,8 +31,9 @@ import type { import { getSchema } from '../../utils/get-schema'; import { getStageHelpLink } from '../../utils/stage'; import type { SortableProps } from '../pipeline-builder-workspace/pipeline-builder-ui-workspace/sortable-list'; -import type { FieldSchema } from '../../utils/get-schema'; +import type { DocumentSchema } from '../../utils/get-schema'; import type { TypeCastTypes } from 'hadron-type-checker'; +import { useFieldsSchema } from '@mongodb-js/compass-field-store'; const containerStyles = css({ display: 'flex', @@ -83,10 +80,11 @@ const warningStyles = css({ type StageWizardProps = SortableProps & { index: number; + namespace: string; useCaseId: string; value: string | null; syntaxError: SyntaxError | null; - fields: WizardComponentProps['fields']; + previousStageFields: DocumentSchema | null; onChange: (value: string) => void; onCancel: () => void; onApply: () => void; @@ -153,16 +151,36 @@ function useGrabFocus() { export const StageWizard = ({ index, + namespace, useCaseId, value, syntaxError, - fields, + previousStageFields, onChange, onCancel: onCancelProp, onApply, ...sortableProps }: StageWizardProps) => { const { returnFocus, focusContainerRef } = useGrabFocus(); + const fieldsSchema = useFieldsSchema(namespace); + const fields: DocumentSchema = useMemo(() => { + function schemaTypeToKindaTypeCastType(type: string | string[]) { + // we don't expect any handling for multi-type schemas, pick the first + // type whatever it is + type = Array.isArray(type) ? type[0] : type; + // parsed schema has the bson type Object replaced with Document to avoid + // collision with JS Objects but that shouldn't be a problem for us + // because we use these as string values alongside well defined casters. + return type === 'Document' ? 'Object' : (type as TypeCastTypes); + } + + return Object.values(fieldsSchema).map((field) => { + return { + name: field.name, + type: schemaTypeToKindaTypeCastType(field.type), + }; + }); + }, [fieldsSchema]); const onCancel = useCallback(() => { onCancelProp?.(); @@ -219,7 +237,7 @@ export const StageWizard = ({
@@ -257,8 +275,8 @@ type WizardOwnProps = { export default connect( (state: RootState, ownProps: WizardOwnProps) => { const { + namespace, autoPreview, - fields: initialFields, pipelineBuilder: { stageEditor: { stages }, }, @@ -271,40 +289,22 @@ export default connect( .reverse() .find((x): x is StoreStage => x.type === 'stage' && !x.disabled); - const mappedInitialFields = ( - initialFields as { - name: string; - description?: string; - }[] - ).map(({ name, description }) => { - // parsed schema has the bson type Object replaced with - // Document to avoid collision with JS Objects but that - // shouldn't be a problem for us because we use these - // as string values alongside well defined casters. - const type = - description === 'Document' - ? 'Object' - : ((description ?? 'String') as TypeCastTypes); - - return { name, type }; - }); - const previousStageFieldsWithSchema = getSchema( - previousStage?.previewDocs?.map((doc) => { - return doc.generateObject(); - }) ?? [] - ); - - const fields = - previousStageFieldsWithSchema.length > 0 && autoPreview - ? previousStageFieldsWithSchema - : mappedInitialFields; - return { id: wizard.id, + namespace, syntaxError: wizard.syntaxError, useCaseId: wizard.useCaseId, value: wizard.value, - fields, + previousStageFields: + autoPreview && + previousStage?.previewDocs && + previousStage?.previewDocs.length > 0 + ? getSchema( + previousStage.previewDocs.map((doc) => { + return doc.generateObject(); + }) + ) + : null, }; }, (dispatch: PipelineBuilderThunkDispatch, ownProps: WizardOwnProps) => ({ diff --git a/packages/compass-aggregations/src/modules/fields.spec.ts b/packages/compass-aggregations/src/modules/fields.spec.ts deleted file mode 100644 index 58d9dc40d6a..00000000000 --- a/packages/compass-aggregations/src/modules/fields.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import reducer, { fieldsChanged, FIELDS_CHANGED } from './fields'; -import { expect } from 'chai'; - -describe('server version module', function () { - const fields = [ - { name: '_id', value: '_id', score: 1, meta: 'field', version: '0.0.0' }, - { name: 'name', value: 'name', score: 1, meta: 'field', version: '0.0.0' }, - ]; - - describe('#fieldsChanged', function () { - it('returns the FIELDS_CHANGED action', function () { - expect(fieldsChanged(fields)).to.deep.equal({ - type: FIELDS_CHANGED, - fields: fields, - }); - }); - }); - - describe('#reducer', function () { - context('when the action is not fields changed', function () { - it('returns the default state', function () { - expect(reducer(undefined, { type: 'test' } as any)).to.deep.equal([]); - }); - }); - - context('when the action is fields changed', function () { - it('returns the new state', function () { - expect(reducer(undefined, fieldsChanged(fields))).to.deep.equal([ - { - name: '_id', - value: '_id', - score: 1, - meta: 'field', - version: '0.0.0', - }, - { - name: 'name', - value: 'name', - score: 1, - meta: 'field', - version: '0.0.0', - }, - ]); - }); - }); - }); -}); diff --git a/packages/compass-aggregations/src/modules/fields.ts b/packages/compass-aggregations/src/modules/fields.ts deleted file mode 100644 index c1441fcc15a..00000000000 --- a/packages/compass-aggregations/src/modules/fields.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { AnyAction } from 'redux'; -import { isAction } from '../utils/is-action'; - -/** - * Fields changed action. - */ -export const FIELDS_CHANGED = 'aggregations/fields/FIELDS_CHANGED' as const; -interface FieldsChangedAction { - type: typeof FIELDS_CHANGED; - fields: { name: string }[]; -} -export type FieldsAction = FieldsChangedAction; -export type FieldsState = { name: string }[]; - -/** - * The initial state. - */ -export const INITIAL_STATE: FieldsState = []; - -/** - * Reducer function for handle state changes to fields. - */ -export default function reducer( - state: FieldsState = INITIAL_STATE, - action: AnyAction -): FieldsState { - if (isAction(action, FIELDS_CHANGED)) { - return action.fields; - } - return state; -} - -/** - * Action creator for fields changed events. - */ -export const fieldsChanged = ( - fields: { name: string }[] -): FieldsChangedAction => ({ - type: FIELDS_CHANGED, - fields: fields, -}); diff --git a/packages/compass-aggregations/src/modules/index.ts b/packages/compass-aggregations/src/modules/index.ts index d2e3ce5429e..4658cd9b10e 100644 --- a/packages/compass-aggregations/src/modules/index.ts +++ b/packages/compass-aggregations/src/modules/index.ts @@ -2,7 +2,6 @@ import type { Action, AnyAction } from 'redux'; import { combineReducers } from 'redux'; import type { AtlasService } from '@mongodb-js/atlas-service/renderer'; import dataService from './data-service'; -import fields from './fields'; import editViewName from './edit-view-name'; import sourceName from './source-name'; import pipelineBuilder from './pipeline-builder'; @@ -53,7 +52,6 @@ const rootReducer = combineReducers({ comments, autoPreview, dataService, - fields, inputDocuments, namespace, env, diff --git a/packages/compass-aggregations/src/stores/store.spec.ts b/packages/compass-aggregations/src/stores/store.spec.ts index ba6e6036fc1..3681862598d 100644 --- a/packages/compass-aggregations/src/stores/store.spec.ts +++ b/packages/compass-aggregations/src/stores/store.spec.ts @@ -77,7 +77,6 @@ describe('Aggregation Store', function () { name: INITIAL_STATE.name, id: INITIAL_STATE.id, savedPipeline: INITIAL_STATE.savedPipeline, - fields: INITIAL_STATE.fields, inputDocuments: { ...INITIAL_STATE.inputDocuments, isLoading: true, @@ -120,66 +119,6 @@ describe('Aggregation Store', function () { }); }); - context('when the fields change', function () { - it('updates the fields', function (done) { - const unsubscribe = store.subscribe(() => { - unsubscribe(); - expect(store.getState().fields).to.deep.equal([ - { - name: 'harry', - value: 'harry', - score: 1, - meta: 'field', - version: '0.0.0', - }, - { - name: 'potter', - value: 'potter', - score: 1, - meta: 'field', - version: '0.0.0', - }, - ]); - done(); - }); - - globalAppRegistry.emit('fields-changed', { - ns: 'test.test', - fields: { - harry: { - name: 'harry', - path: ['harry'], - count: 1, - type: 'Number', - }, - potter: { - name: 'potter', - path: ['potter'], - count: 1, - type: 'Boolean', - }, - }, - topLevelFields: ['harry', 'potter'], - autocompleteFields: [ - { - name: 'harry', - value: 'harry', - score: 1, - meta: 'field', - version: '0.0.0', - }, - { - name: 'potter', - value: 'potter', - score: 1, - meta: 'field', - version: '0.0.0', - }, - ], - }); - }); - }); - context('when an aggregation should be generated from query', function () { it('updates the ai store', function (done) { const unsubscribe = store.subscribe(() => { diff --git a/packages/compass-aggregations/src/stores/store.ts b/packages/compass-aggregations/src/stores/store.ts index 9e9773188cd..45a528aebb2 100644 --- a/packages/compass-aggregations/src/stores/store.ts +++ b/packages/compass-aggregations/src/stores/store.ts @@ -6,7 +6,6 @@ import { toJSString } from 'mongodb-query-parser'; import { AtlasService } from '@mongodb-js/atlas-service/renderer'; import type { PipelineBuilderThunkDispatch, RootState } from '../modules'; import reducer from '../modules'; -import { fieldsChanged } from '../modules/fields'; import { refreshInputDocuments } from '../modules/input-documents'; import { openStoredPipeline } from '../modules/saved-pipeline'; import { PipelineBuilder } from '../modules/pipeline-builder/pipeline-builder'; @@ -143,8 +142,6 @@ export function activateAggregationsPlugin( // we use the aggregations plugin inside compass. In that use case we get it // from the instance model above. options.env ?? (instance.env as typeof ENVS[number]), - // options.fields is only used by mms, but always set to [] which is the initial value anyway - fields: options.fields ?? [], // options.outResultsFn is only used by mms outResultsFn: options.outResultsFn, pipelineBuilder: { @@ -209,18 +206,6 @@ export function activateAggregationsPlugin( refreshInput(); }); - /** - * When the schema fields change, update the state with the new - * fields. - * - * @param {Object} fields - The fields. - */ - on(globalAppRegistry, 'fields-changed', (fields) => { - if (fields.ns === options.namespace) { - store.dispatch(fieldsChanged(fields.autocompleteFields)); - } - }); - on(localAppRegistry, 'generate-aggregation-from-query', (data) => { store.dispatch(generateAggregationFromQuery(data)); }); diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index 5c046e5daf5..952b19d9801 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -59,6 +59,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-query-bar": "^8.24.1", "@mongodb-js/compass-workspaces": "^0.4.1", @@ -113,6 +114,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-query-bar": "^8.24.1", "@mongodb-js/compass-workspaces": "^0.4.1", diff --git a/packages/compass-crud/src/components/document-json-view.tsx b/packages/compass-crud/src/components/document-json-view.tsx index 91beb7b7043..df123f54563 100644 --- a/packages/compass-crud/src/components/document-json-view.tsx +++ b/packages/compass-crud/src/components/document-json-view.tsx @@ -22,6 +22,7 @@ const listItemStyles = css({ }); export type DocumentJsonViewProps = { + namespace: string; docs: Document[]; isEditable: boolean; className?: string; @@ -34,7 +35,6 @@ export type DocumentJsonViewProps = { | 'updateDocument' | 'openInsertDocumentDialog' | 'isExpanded' - | 'fields' >; const keylineCardCSS = css({ @@ -58,6 +58,7 @@ class DocumentJsonView extends React.Component {
  • { updateDocument={this.props.updateDocument} openInsertDocumentDialog={this.props.openInsertDocumentDialog} isExpanded={this.props.isExpanded} - fields={this.props.fields} />
  • diff --git a/packages/compass-crud/src/components/json-editor.tsx b/packages/compass-crud/src/components/json-editor.tsx index a583cb4e038..380a5b0b25c 100644 --- a/packages/compass-crud/src/components/json-editor.tsx +++ b/packages/compass-crud/src/components/json-editor.tsx @@ -21,6 +21,7 @@ import { } from '@mongodb-js/compass-editor'; import type { EditorRef, Action } from '@mongodb-js/compass-editor'; import type { CrudActions } from '../stores/crud-store'; +import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; const editorStyles = css({ minHeight: spacing[5] + spacing[3], @@ -49,6 +50,7 @@ const actionsGroupStyles = css({ }); export type JSONEditorProps = { + namespace: string; doc: Document; editable: boolean; isTimeSeries?: boolean; @@ -58,10 +60,10 @@ export type JSONEditorProps = { copyToClipboard?: CrudActions['copyToClipboard']; openInsertDocumentDialog?: CrudActions['openInsertDocumentDialog']; isExpanded?: boolean; - fields?: string[]; }; const JSONEditor: React.FunctionComponent = ({ + namespace, doc, editable, isTimeSeries = false, @@ -70,7 +72,6 @@ const JSONEditor: React.FunctionComponent = ({ copyToClipboard, openInsertDocumentDialog, isExpanded = false, - fields = [], }) => { const darkMode = useDarkMode(); const editorRef = useRef(null); @@ -155,8 +156,14 @@ const JSONEditor: React.FunctionComponent = ({ } }, [isExpanded]); + const fields = useAutocompleteFields(namespace); + const completer = useMemo(() => { - return createDocumentAutocompleter(fields); + return createDocumentAutocompleter( + fields.map((field) => { + return field.name; + }) + ); }, [fields]); const isEditable = editable && !deleting && !isTimeSeries; diff --git a/packages/compass-crud/src/stores/crud-store.spec.ts b/packages/compass-crud/src/stores/crud-store.spec.ts index faa17a293c7..a20f425ccdf 100644 --- a/packages/compass-crud/src/stores/crud-store.spec.ts +++ b/packages/compass-crud/src/stores/crud-store.spec.ts @@ -264,7 +264,6 @@ describe('store', function () { isCollectionScan: false, version: '6.0.0', view: 'List', - fields: [], }); }); }); diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 973243e48f0..d9da52418e2 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -412,7 +412,6 @@ type CrudState = { resultId: number; isWritable: boolean; instanceDescription: string; - fields: string[]; isCollectionScan?: boolean; isSearchIndexesSupported: boolean; isUpdatePreviewSupported: boolean; @@ -468,12 +467,6 @@ class CrudStoreImpl this.logger = services.logger; } - updateFields(fields: { autocompleteFields: { name: string }[] }) { - this.setState({ - fields: fields.autocompleteFields.map((field) => field.name), - }); - } - getInitialState(): CrudState { return { ns: '', @@ -504,7 +497,6 @@ class CrudStoreImpl resultId: resultId(), isWritable: false, instanceDescription: '', - fields: [], isCollectionScan: false, isSearchIndexesSupported: false, isUpdatePreviewSupported: false, @@ -2091,12 +2083,6 @@ export function activateDocumentsPlugin( on(localAppRegistry, 'refresh-data', store.refreshDocuments.bind(store)); - on(globalAppRegistry, 'fields-changed', (fields) => { - if (fields.ns === store.state.ns) { - store.updateFields(fields); - } - }); - on( localAppRegistry, 'favorites-open-bulk-update-favorite', diff --git a/packages/compass-field-store/package.json b/packages/compass-field-store/package.json index 07098b343a4..4f2da228919 100644 --- a/packages/compass-field-store/package.json +++ b/packages/compass-field-store/package.json @@ -21,13 +21,13 @@ ], "license": "SSPL", "main": "dist/index.js", - "compass:main": "src/index.ts", + "compass:main": "src/index.tsx", "exports": { "browser": "./dist/browser.js", "require": "./dist/index.js" }, "compass:exports": { - ".": "./src/index.ts" + ".": "./src/index.tsx" }, "types": "./dist/index.d.ts", "scripts": { @@ -59,6 +59,8 @@ "@mongodb-js/prettier-config-compass": "^1.0.1", "@mongodb-js/tsconfig-compass": "^1.0.3", "@mongodb-js/webpack-config-compass": "^1.3.1", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -71,6 +73,8 @@ "mongodb-schema": "^12.1.0", "nyc": "^15.1.0", "prettier": "^2.7.1", + "react": "^17.0.2", + "react-redux": "^8.1.3", "redux": "^4.2.1", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" @@ -82,6 +86,7 @@ "hadron-app-registry": "^9.1.4" }, "peerDependencies": { - "hadron-app-registry": "^9.1.4" + "hadron-app-registry": "^9.1.4", + "react": "^17.0.2" } } diff --git a/packages/compass-field-store/src/index.spec.ts b/packages/compass-field-store/src/index.spec.ts new file mode 100644 index 00000000000..945d7a0e948 --- /dev/null +++ b/packages/compass-field-store/src/index.spec.ts @@ -0,0 +1,70 @@ +import { renderHook, cleanup } from '@testing-library/react-hooks'; +import { waitFor } from '@testing-library/react'; +import FieldStorePlugin from './'; +import { useAutocompleteFields } from './'; +import { expect } from 'chai'; +import AppRegistry from 'hadron-app-registry'; + +describe('useAutocompleteFields', function () { + let appRegistry: AppRegistry; + let Plugin: ReturnType; + + beforeEach(function () { + appRegistry = new AppRegistry(); + Plugin = FieldStorePlugin.withMockServices({ + globalAppRegistry: appRegistry, + }); + }); + + afterEach(cleanup); + + it('returns empty list when namespace schema is not available', function () { + const { result } = renderHook(() => useAutocompleteFields('foo.bar'), { + wrapper: Plugin, + }); + + expect(result.current).to.deep.eq([]); + }); + + it('updates when fields are added', async function () { + const { result } = renderHook(() => useAutocompleteFields('foo.bar'), { + wrapper: Plugin, + }); + + appRegistry.emit('document-inserted', { + ns: 'foo.bar', + docs: [{ foo: 1 }, { bar: false }, { buz: 'str' }], + }); + + await waitFor(() => { + expect(result.current).have.lengthOf(3); + }); + + expect(result.current).to.deep.eq([ + { + name: 'bar', + value: 'bar', + score: 1, + meta: 'field', + version: '0.0.0', + description: 'Boolean | Undefined', + }, + { + name: 'buz', + value: 'buz', + score: 1, + meta: 'field', + version: '0.0.0', + description: 'String | Undefined', + }, + { + name: 'foo', + value: 'foo', + score: 1, + meta: 'field', + version: '0.0.0', + description: 'Number | Undefined', + }, + ]); + }); +}); diff --git a/packages/compass-field-store/src/index.ts b/packages/compass-field-store/src/index.ts deleted file mode 100644 index 6169dedf0a9..00000000000 --- a/packages/compass-field-store/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; -import { activatePlugin } from './stores/store'; - -const FieldStorePlugin = registerHadronPlugin({ - name: 'FieldStore', - component() { - // FieldStore plugin doesn't render anything, just subscribes to the events - // and emits its own - return null; - }, - activate: activatePlugin, -}); - -export default FieldStorePlugin; diff --git a/packages/compass-field-store/src/index.tsx b/packages/compass-field-store/src/index.tsx new file mode 100644 index 00000000000..6eb1c4c3325 --- /dev/null +++ b/packages/compass-field-store/src/index.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { registerHadronPlugin } from 'hadron-app-registry'; +import { activatePlugin } from './stores/store'; + +const FieldStorePlugin = registerHadronPlugin({ + name: 'FieldStore', + component({ children }) { + // FieldStore plugin doesn't render anything, but keeps track of changes to + // the namespace documents and maintains a schema to be used with + // autocompleters + return <>{children}; + }, + activate: activatePlugin, +}); + +export { useAutocompleteFields, useFieldsSchema } from './stores/context'; +export default FieldStorePlugin; diff --git a/packages/compass-field-store/src/modules/fields.ts b/packages/compass-field-store/src/modules/fields.ts index 29a61082147..c02205a1cb7 100644 --- a/packages/compass-field-store/src/modules/fields.ts +++ b/packages/compass-field-store/src/modules/fields.ts @@ -133,6 +133,14 @@ export function mergeSchema( export function schemaFieldsToAutocompleteItems( fields: Record ) { + function stringifyType(type: string | string[]) { + return typeof type === 'string' + ? type + : // Type can be an array of arrays, values can also be duplicated between + // arrays of types, so we pick only unique ones + uniq(type.flat()).join(' | '); + } + return Object.entries(fields).map(([key, field]) => { return { name: key, @@ -140,9 +148,7 @@ export function schemaFieldsToAutocompleteItems( score: 1, meta: 'field', version: '0.0.0', - description: Array.isArray(field.type) - ? field.type.join(' | ') - : field.type, + description: stringifyType(field.type), }; }); } diff --git a/packages/compass-field-store/src/stores/context.ts b/packages/compass-field-store/src/stores/context.ts new file mode 100644 index 00000000000..1340c679e3e --- /dev/null +++ b/packages/compass-field-store/src/stores/context.ts @@ -0,0 +1,45 @@ +import React, { useMemo } from 'react'; +import type { ReactReduxContextValue, TypedUseSelectorHook } from 'react-redux'; +import { createSelectorHook } from 'react-redux'; +import type { FieldsState } from '../modules'; +import type { SchemaFieldSubset } from '../modules/fields'; +import { schemaFieldsToAutocompleteItems } from '../modules/fields'; + +export const FieldStoreContext = React.createContext< + ReactReduxContextValue +>( + // @ts-expect-error react-redux types + null +); + +const useSelector: TypedUseSelectorHook = + createSelectorHook(FieldStoreContext); + +const EMPTY_FIELDS_OBJECT = Object.create(null); + +export function useFieldsSchema( + namespace: string +): Readonly> { + let fields: Record = EMPTY_FIELDS_OBJECT; + try { + fields = useSelector( + (state) => state[namespace]?.fields ?? EMPTY_FIELDS_OBJECT + ); + } catch (err) { + // We can only end up with an error here if store is missing in context, + // this is safe to ignore in test environment + if (process.env.NODE_ENV !== 'test') { + throw err; + } + } + return fields; +} + +export function useAutocompleteFields( + namespace: string +): ReturnType { + const fields = useFieldsSchema(namespace); + return useMemo(() => { + return schemaFieldsToAutocompleteItems(fields); + }, [fields]); +} diff --git a/packages/compass-field-store/src/stores/store.ts b/packages/compass-field-store/src/stores/store.ts index 8af8ed239c9..4c9ef3a2553 100644 --- a/packages/compass-field-store/src/stores/store.ts +++ b/packages/compass-field-store/src/stores/store.ts @@ -4,9 +4,10 @@ import type { Schema } from 'mongodb-schema'; import parseSchema from 'mongodb-schema'; import type { AppRegistry, ActivateHelpers } from 'hadron-app-registry'; import { schemaFieldsToAutocompleteItems } from '../modules/fields'; +import { FieldStoreContext } from './context'; export function activatePlugin( - _: unknown, + _initialProps: unknown, { globalAppRegistry }: { globalAppRegistry: AppRegistry }, { on, cleanup }: ActivateHelpers ) { @@ -52,5 +53,5 @@ export function activatePlugin( } ); - return { store, deactivate: cleanup }; + return { store, deactivate: cleanup, context: FieldStoreContext }; } diff --git a/packages/compass-field-store/tsconfig.json b/packages/compass-field-store/tsconfig.json index b2d3c80179f..79bc84584ce 100644 --- a/packages/compass-field-store/tsconfig.json +++ b/packages/compass-field-store/tsconfig.json @@ -1,8 +1,7 @@ { - "extends": "@mongodb-js/tsconfig-compass/tsconfig.common.json", + "extends": "@mongodb-js/tsconfig-compass/tsconfig.react.json", "compilerOptions": { - "outDir": "dist", - "allowJs": true + "outDir": "dist" }, "include": ["src/**/*"], "exclude": ["./src/**/*.spec.*"] diff --git a/packages/compass-home/src/components/home.tsx b/packages/compass-home/src/components/home.tsx index c2a6c54bc06..50e86bd92a1 100644 --- a/packages/compass-home/src/components/home.tsx +++ b/packages/compass-home/src/components/home.tsx @@ -324,11 +324,12 @@ function Home({ - - + + + diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 2acd4302929..f4e6a17c046 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -59,6 +59,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-workspaces": "^0.4.1", "@mongodb-js/mongodb-constants": "^0.8.7", @@ -102,6 +103,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/compass-workspaces": "^0.4.1", "@mongodb-js/mongodb-constants": "^0.8.7", diff --git a/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx b/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx index 3343a0f8d44..1ab4a0b7937 100644 --- a/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx +++ b/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { css, spacing, Accordion, Body } from '@mongodb-js/compass-components'; - +import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; import { CreateIndexFields } from '../create-index-fields'; import { hasColumnstoreIndexesSupport } from '../../utils/columnstore-indexes'; import CheckboxInput from './checkbox-input'; @@ -21,11 +21,9 @@ const createIndexModalOptionStyles = css({ type IndexField = { name: string; type: string }; type CreateIndexFormProps = { + namespace: string; fields: IndexField[]; - schemaFields: string[]; - serverVersion: string; - updateFieldName: (idx: number, name: string) => void; updateFieldType: (idx: number, fType: string) => void; addField: () => void; // Plus icon. @@ -33,16 +31,25 @@ type CreateIndexFormProps = { }; function CreateIndexForm({ + namespace, fields, - schemaFields, - serverVersion, - updateFieldName, updateFieldType, addField, removeField, }: CreateIndexFormProps) { + const schemaFields = useAutocompleteFields(namespace); + const schemaFieldNames = useMemo(() => { + return schemaFields + .filter((field) => { + return field.name !== '_id'; + }) + .map((field) => { + return field.name; + }); + }, [schemaFields]); + return ( <>
    {fields.length > 0 ? ( 1)} diff --git a/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx b/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx index ffe8cef0871..803e7ac9d09 100644 --- a/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx +++ b/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx @@ -14,7 +14,6 @@ import { updateFieldType, updateFieldName, } from '../../modules/create-index/fields'; -import { changeSchemaFields } from '../../modules/create-index/schema-fields'; import { clearError } from '../../modules/create-index/error'; import { createIndex, closeCreateIndexModal } from '../../modules/create-index'; import { CreateIndexForm } from '../create-index-form/create-index-form'; @@ -74,7 +73,7 @@ function CreateIndexModal({ - + @@ -91,15 +90,7 @@ function CreateIndexModal({ } const mapState = ( - { - fields, - inProgress, - schemaFields, - error, - isVisible, - namespace, - serverVersion, - }: RootState, + { fields, inProgress, error, isVisible, namespace, serverVersion }: RootState, // To make sure the derived type is correctly including plugin metadata passed // by CollectionTab // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -107,7 +98,6 @@ const mapState = ( ) => ({ fields, inProgress, - schemaFields, error, isVisible, namespace, @@ -115,7 +105,6 @@ const mapState = ( }); const mapDispatch = { - changeSchemaFields, clearError, createIndex, closeCreateIndexModal, diff --git a/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.spec.tsx b/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.spec.tsx index c8d506e1927..b64364bce76 100644 --- a/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.spec.tsx +++ b/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.spec.tsx @@ -29,6 +29,7 @@ describe('Base Search Index Modal', function () { render( ); }); diff --git a/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx b/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx index 27d089ec2ef..73b71ced48f 100644 --- a/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx +++ b/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx @@ -35,7 +35,7 @@ import type { Document } from 'mongodb'; import { useTrackOnChange } from '@mongodb-js/compass-logging/provider'; import { SearchIndexTemplateDropdown } from '../search-index-template-dropdown'; import type { SearchTemplate } from '@mongodb-js/mongodb-constants'; -import type { Field } from '../../modules/fields'; +import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; // Copied from packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/utils.ts function parseShellBSON(source: string): Document[] { @@ -95,13 +95,13 @@ type ParsingError = { }; type BaseSearchIndexModalProps = { + namespace: string; mode: 'create' | 'update'; initialIndexName: string; initialIndexDefinition: string; isModalOpen: boolean; isBusy: boolean; error: string | undefined; - fields: Field[]; onSubmit: (indexName: string, indexDefinition: Document) => void; onClose: () => void; }; @@ -109,13 +109,13 @@ type BaseSearchIndexModalProps = { export const BaseSearchIndexModal: React.FunctionComponent< BaseSearchIndexModalProps > = ({ + namespace, mode, initialIndexName, initialIndexDefinition, isModalOpen, isBusy, error, - fields, onSubmit, onClose, }) => { @@ -204,13 +204,11 @@ export const BaseSearchIndexModal: React.FunctionComponent< [editorRef] ); - const completer = useMemo( - () => - createSearchIndexAutocompleter({ - fields, - }), - [fields] - ); + const fields = useAutocompleteFields(namespace); + + const completer = useMemo(() => { + return createSearchIndexAutocompleter({ fields }); + }, [fields]); return ( void; onCloseModal: () => void; }; export const CreateSearchIndexModal: React.FunctionComponent< CreateSearchIndexModalProps -> = ({ isModalOpen, isBusy, error, fields, onCreateIndex, onCloseModal }) => { +> = ({ + namespace, + isModalOpen, + isBusy, + error, + onCreateIndex, + onCloseModal, +}) => { return ( @@ -40,15 +46,15 @@ export const CreateSearchIndexModal: React.FunctionComponent< }; const mapState = ({ + namespace, searchIndexes: { createIndex: { isBusy, isModalOpen, error }, }, - fields, }: RootState) => ({ + namespace, isModalOpen, isBusy, error, - fields, }); const mapDispatch = { diff --git a/packages/compass-indexes/src/components/search-indexes-modals/update-search-index-modal.tsx b/packages/compass-indexes/src/components/search-indexes-modals/update-search-index-modal.tsx index 8b0cb10996b..0d7252e6e34 100644 --- a/packages/compass-indexes/src/components/search-indexes-modals/update-search-index-modal.tsx +++ b/packages/compass-indexes/src/components/search-indexes-modals/update-search-index-modal.tsx @@ -4,15 +4,14 @@ import { connect } from 'react-redux'; import type { RootState } from '../../modules'; import type { Document } from 'mongodb'; import { BaseSearchIndexModal } from './base-search-index-modal'; -import type { Field } from '../../modules/fields'; type UpdateSearchIndexModalProps = { + namespace: string; indexName: string; indexDefinition: string; isModalOpen: boolean; isBusy: boolean; error: string | undefined; - fields: Field[]; onUpdateIndex: (indexName: string, indexDefinition: Document) => void; onCloseModal: () => void; }; @@ -20,24 +19,24 @@ type UpdateSearchIndexModalProps = { export const UpdateSearchIndexModal: React.FunctionComponent< UpdateSearchIndexModalProps > = ({ + namespace, indexName, indexDefinition, isModalOpen, isBusy, error, - fields, onUpdateIndex, onCloseModal, }) => { return ( @@ -45,12 +44,13 @@ export const UpdateSearchIndexModal: React.FunctionComponent< }; const mapState = ({ + namespace, searchIndexes: { indexes, updateIndex: { indexName, isBusy, isModalOpen, error }, }, - fields, }: RootState) => ({ + namespace, isModalOpen, isBusy, indexName, @@ -60,7 +60,6 @@ const mapState = ({ 2 ), error, - fields, }); const mapDispatch = { diff --git a/packages/compass-indexes/src/modules/create-index/index.ts b/packages/compass-indexes/src/modules/create-index/index.ts index c3cd3c71909..607fabc5961 100644 --- a/packages/compass-indexes/src/modules/create-index/index.ts +++ b/packages/compass-indexes/src/modules/create-index/index.ts @@ -11,7 +11,6 @@ import type { IndexField } from '../create-index/fields'; import namespace from '../namespace'; import serverVersion from '../server-version'; import type { InProgressIndex } from '../regular-indexes'; -import schemaFields from '../create-index/schema-fields'; import { resetForm } from '../reset-form'; import options from './options'; import { hasColumnstoreIndex } from '../../utils/columnstore-indexes'; @@ -37,9 +36,8 @@ const reducer = combineReducers({ inProgress, isVisible, - // fields realted + // form fields realted fields, - schemaFields, // validation error, diff --git a/packages/compass-indexes/src/modules/create-index/schema-fields.spec.js b/packages/compass-indexes/src/modules/create-index/schema-fields.spec.js deleted file mode 100644 index 93265c8242b..00000000000 --- a/packages/compass-indexes/src/modules/create-index/schema-fields.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import { expect } from 'chai'; - -import reducer, { - INITIAL_STATE, - changeSchemaFields, - CHANGE_SCHEMA_FIELDS, -} from '../create-index/schema-fields'; - -describe('create index schema fields module', function () { - describe('#reducer', function () { - context('when an action is provided', function () { - it('returns the new state', function () { - expect( - reducer( - undefined, - changeSchemaFields(['FIELD1', 'FIELD2', 'FIELD1.TEST']) - ) - ).to.deep.equal(['FIELD1', 'FIELD2', 'FIELD1.TEST']); - }); - }); - - context('when an action is not provided', function () { - it('returns the default state', function () { - expect(reducer(undefined, {})).to.equal(INITIAL_STATE); - }); - }); - }); - - describe('#changeSchemaFields', function () { - it('returns the action', function () { - expect( - changeSchemaFields(['FIELD1', 'FIELD2', 'FIELD1.TEST']) - ).to.deep.equal({ - type: CHANGE_SCHEMA_FIELDS, - schemaFields: ['FIELD1', 'FIELD2', 'FIELD1.TEST'], - }); - }); - }); -}); diff --git a/packages/compass-indexes/src/modules/create-index/schema-fields.ts b/packages/compass-indexes/src/modules/create-index/schema-fields.ts deleted file mode 100644 index 66f24ee2558..00000000000 --- a/packages/compass-indexes/src/modules/create-index/schema-fields.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { AnyAction } from 'redux'; - -/** - * Change schema fields action name. - */ -export const CHANGE_SCHEMA_FIELDS = - 'indexes/create-index/schema-fields/CHANGE_SCHEMA_FIELDS'; - -/** - * The initial state of the schema fields. - */ -export const INITIAL_STATE: string[] = []; - -/** - * Reducer function for handle the schema fields state changes. - * - * @param state - The create schema fields state. - * @param action - The action. - * - * @returns The new state. - */ -export default function reducer( - state = INITIAL_STATE, - action: AnyAction -): string[] { - if (action.type === CHANGE_SCHEMA_FIELDS) { - return action.schemaFields; - } - return state; -} - -/** - * The change schema fields action creator. - * - * @param schemaFields - The schema fields. - * - * @returns The action. - */ -export const changeSchemaFields = (schemaFields: string[]) => ({ - type: CHANGE_SCHEMA_FIELDS, - schemaFields, -}); diff --git a/packages/compass-indexes/src/modules/fields.ts b/packages/compass-indexes/src/modules/fields.ts deleted file mode 100644 index 2437c7e50c1..00000000000 --- a/packages/compass-indexes/src/modules/fields.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { AnyAction } from 'redux'; -import { isAction } from './../utils/is-action'; - -export enum ActionTypes { - SetFields = 'indexes/SetFields', -} - -export type Field = { - name: string; - description: string; -}; - -type SetFieldsAction = { - type: ActionTypes.SetFields; - fields: Field[]; -}; - -type State = Field[]; - -export const INITIAL_STATE: State = []; - -export default function reducer(state = INITIAL_STATE, action: AnyAction) { - if (isAction(action, ActionTypes.SetFields)) { - return action.fields; - } - return state; -} - -export const setFields = (fields: Field[]): SetFieldsAction => ({ - type: ActionTypes.SetFields, - fields, -}); diff --git a/packages/compass-indexes/src/modules/index.ts b/packages/compass-indexes/src/modules/index.ts index 532694012a5..8262acec3ca 100644 --- a/packages/compass-indexes/src/modules/index.ts +++ b/packages/compass-indexes/src/modules/index.ts @@ -10,7 +10,6 @@ import regularIndexes from './regular-indexes'; import searchIndexes from './search-indexes'; import serverVersion from './server-version'; import namespace from './namespace'; -import fields from './fields'; import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging'; @@ -24,7 +23,6 @@ const reducer = combineReducers({ namespace, regularIndexes, searchIndexes, - fields, }); export type SortDirection = 'asc' | 'desc'; diff --git a/packages/compass-indexes/src/stores/create-index.spec.js b/packages/compass-indexes/src/stores/create-index.spec.js deleted file mode 100644 index d6f1b055350..00000000000 --- a/packages/compass-indexes/src/stores/create-index.spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; -import { expect } from 'chai'; -import { activatePlugin } from './create-index'; - -describe('CreateIndexStore [Store]', function () { - const appRegistry = new AppRegistry(); - let store; - let deactivate; - - function configureStore() { - ({ store, deactivate } = activatePlugin( - { - namespace: 'db.coll', - serverVersion: '0.0.0', - }, - { globalAppRegistry: appRegistry, localAppRegistry: appRegistry }, - createActivateHelpers() - )); - } - - afterEach(function () { - deactivate(); - appRegistry.deactivate(); - }); - - context('when the field-store triggers', function () { - beforeEach(function () { - configureStore(); - appRegistry.emit('fields-changed', { - ns: 'db.coll', - fields: { a: 1, b: 2 }, - topLevelFields: ['a'], - autocompleteFields: ['a', 'b'], - }); - }); - it('dispatches the changeSchemaFields action', function () { - expect(store.getState().schemaFields).to.deep.equal(['a', 'b']); - }); - }); -}); diff --git a/packages/compass-indexes/src/stores/create-index.ts b/packages/compass-indexes/src/stores/create-index.ts index a0b836232d6..087e1ed7cc1 100644 --- a/packages/compass-indexes/src/stores/create-index.ts +++ b/packages/compass-indexes/src/stores/create-index.ts @@ -1,7 +1,6 @@ import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from '../modules/create-index'; -import { changeSchemaFields } from '../modules/create-index/schema-fields'; import { toggleIsVisible } from '../modules/is-visible'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import { type ActivateHelpers } from 'hadron-app-registry'; @@ -23,12 +22,7 @@ export type CreateIndexPluginServices = { export function activatePlugin( { namespace, serverVersion }: CreateIndexPluginOptions, - { - globalAppRegistry, - localAppRegistry, - dataService, - logger, - }: CreateIndexPluginServices, + { localAppRegistry, dataService, logger }: CreateIndexPluginServices, { on, cleanup }: ActivateHelpers ) { const store = createStore( @@ -39,16 +33,6 @@ export function activatePlugin( ) ); - on(globalAppRegistry, 'fields-changed', (state) => { - if (state.ns === namespace) { - store.dispatch( - changeSchemaFields( - Object.keys(state.fields).filter((name) => name !== '_id') - ) - ); - } - }); - on(localAppRegistry, 'open-create-index-modal', () => { store.dispatch(toggleIsVisible(true)); }); diff --git a/packages/compass-indexes/src/stores/store.ts b/packages/compass-indexes/src/stores/store.ts index ea2568f5036..71ecdab155f 100644 --- a/packages/compass-indexes/src/stores/store.ts +++ b/packages/compass-indexes/src/stores/store.ts @@ -21,7 +21,6 @@ import { } from '../modules/search-indexes'; import type { DataService } from 'mongodb-data-service'; import type AppRegistry from 'hadron-app-registry'; -import { setFields } from '../modules/fields'; import { switchToRegularIndexes } from '../modules/index-view'; import type { ActivateHelpers } from 'hadron-app-registry'; import type { MongoDBInstance } from '@mongodb-js/compass-app-stores/provider'; @@ -76,7 +75,6 @@ export function activateIndexesPlugin( namespace: options.namespace, serverVersion: options.serverVersion, isReadonlyView: options.isReadonly, - fields: [], indexView: INDEX_LIST_INITIAL_STATE, searchIndexes: { ...SEARCH_INDEXES_INITIAL_STATE, @@ -119,12 +117,6 @@ export function activateIndexesPlugin( } ); - on(globalAppRegistry, 'fields-changed', (fields) => { - if (fields.ns === options.namespace) { - store.dispatch(setFields(fields.autocompleteFields)); - } - }); - on(localAppRegistry, 'open-create-search-index-modal', () => { store.dispatch(showCreateModal()); }); diff --git a/packages/compass-query-bar/package.json b/packages/compass-query-bar/package.json index f6484d94a60..db97e188765 100644 --- a/packages/compass-query-bar/package.json +++ b/packages/compass-query-bar/package.json @@ -60,6 +60,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/mongodb-constants": "^0.8.7", @@ -106,6 +107,7 @@ "@mongodb-js/compass-app-stores": "^7.9.1", "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-generative-ai": "^0.7.1", "@mongodb-js/compass-logging": "^1.2.10", "@mongodb-js/mongodb-constants": "^0.8.7", diff --git a/packages/compass-query-bar/src/components/option-editor.spec.tsx b/packages/compass-query-bar/src/components/option-editor.spec.tsx index 482c0245e14..f59d8230eab 100644 --- a/packages/compass-query-bar/src/components/option-editor.spec.tsx +++ b/packages/compass-query-bar/src/components/option-editor.spec.tsx @@ -31,6 +31,7 @@ describe('OptionEditor', function () { it('fills the input with an empty object "{}" when empty on focus', async function () { render( {}} value="" @@ -49,6 +50,7 @@ describe('OptionEditor', function () { it('does not change input value when empty on focus', async function () { render( {}} value="{ foo: 1 }" @@ -67,6 +69,7 @@ describe('OptionEditor', function () { it('should adjust pasted query if pasting over empty brackets with the cursor in the middle', async function () { render( {}} value="" @@ -91,6 +94,7 @@ describe('OptionEditor', function () { it('should not modify user text whe pasting when cursor moved', async function () { render( {}} value="" @@ -117,6 +121,7 @@ describe('OptionEditor', function () { it('should not modify user text when pasting in empty input', async function () { render( {}} value="" diff --git a/packages/compass-query-bar/src/components/option-editor.tsx b/packages/compass-query-bar/src/components/option-editor.tsx index e8c48f2bc4c..0f6fda6098c 100644 --- a/packages/compass-query-bar/src/components/option-editor.tsx +++ b/packages/compass-query-bar/src/components/option-editor.tsx @@ -9,11 +9,7 @@ import { SignalPopover, rafraf, } from '@mongodb-js/compass-components'; -import type { - Command, - CompletionWithServerInfo, - EditorRef, -} from '@mongodb-js/compass-editor'; +import type { Command, EditorRef } from '@mongodb-js/compass-editor'; import { CodemirrorInlineEditor as InlineEditor, createQueryAutocompleter, @@ -22,6 +18,7 @@ import { connect } from '../stores/context'; import { usePreference } from 'compass-preferences-model'; import { lenientlyFixQuery } from '../query/leniently-fix-query'; import type { RootState } from '../stores/query-bar-store'; +import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; const editorContainerStyles = css({ position: 'relative', @@ -80,6 +77,7 @@ const insightsBadgeStyles = css({ }); type OptionEditorProps = { + namespace: string; id?: string; hasError?: boolean; /** @@ -91,7 +89,6 @@ type OptionEditorProps = { onApply?(): void; onBlur?(): void; placeholder?: string | HTMLElement; - schemaFields?: CompletionWithServerInfo[]; serverVersion?: string; value?: string; ['data-testid']?: string; @@ -99,6 +96,7 @@ type OptionEditorProps = { }; export const OptionEditor: React.FunctionComponent = ({ + namespace, id, hasError = false, insertEmptyDocOnFocus = true, @@ -106,7 +104,6 @@ export const OptionEditor: React.FunctionComponent = ({ onApply, onBlur, placeholder, - schemaFields = [], serverVersion = '3.6.0', value = '', ['data-testid']: dataTestId, @@ -138,17 +135,11 @@ export const OptionEditor: React.FunctionComponent = ({ ]; }, []); + const schemaFields = useAutocompleteFields(namespace); + const completer = useMemo(() => { return createQueryAutocompleter({ - fields: schemaFields - .filter( - (field): field is { name: string } & CompletionWithServerInfo => - !!field.name - ) - .map((field) => ({ - name: field.name, - description: field.description, - })), + fields: schemaFields, serverVersion, }); }, [schemaFields, serverVersion]); @@ -223,7 +214,7 @@ export const OptionEditor: React.FunctionComponent = ({ const ConnectedOptionEditor = connect((state: RootState) => { return { - schemaFields: state.queryBar.schemaFields as CompletionWithServerInfo[], + namespace: state.queryBar.namespace, serverVersion: state.queryBar.serverVersion, }; })(OptionEditor); diff --git a/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts b/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts index a99416cbb6b..500f722fbe5 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts @@ -9,7 +9,6 @@ import { applyFromHistory, applyQuery, changeField, - changeSchemaFields, explainQuery, resetQuery, setQuery, @@ -204,19 +203,6 @@ describe('queryBarReducer', function () { }); }); - describe('changeSchemaFields', function () { - it('should save fields in the store', function () { - expect(store.getState().queryBar) - .to.have.property('schemaFields') - .deep.eq([]); - const fields = [{ name: 'a' }, { name: 'b' }, { name: 'c' }]; - store.dispatch(changeSchemaFields(fields)); - expect(store.getState().queryBar) - .to.have.property('schemaFields') - .deep.eq(fields); - }); - }); - describe('applyFromHistory', function () { it('should reset query to whatever was passed in the action', function () { const newQuery = { diff --git a/packages/compass-query-bar/src/stores/query-bar-reducer.ts b/packages/compass-query-bar/src/stores/query-bar-reducer.ts index d240ba0565c..1b0fa1f1720 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -34,7 +34,6 @@ type QueryBarState = { fields: QueryFormFields; expanded: boolean; serverVersion: string; - schemaFields: unknown[]; lastAppliedQuery: BaseQuery | null; /** * For testing purposes only, allows to track whether or not apply button was @@ -53,7 +52,6 @@ export const INITIAL_STATE: QueryBarState = { fields: mapQueryToFormFields({}, DEFAULT_FIELD_VALUES), expanded: false, serverVersion: '3.6.0', - schemaFields: [], lastAppliedQuery: null, applyId: 0, namespace: '', @@ -66,7 +64,6 @@ export enum QueryBarActions { ChangeReadonlyConnectionStatus = 'compass-query-bar/ChangeReadonlyConnectionStatus', ToggleQueryOptions = 'compass-query-bar/ToggleQueryOptions', ChangeField = 'compass-query-bar/ChangeField', - ChangeSchemaFields = 'compass-query-bar/ChangeSchemaFields', SetQuery = 'compass-query-bar/SetQuery', ApplyQuery = 'compass-query-bar/ApplyQuery', ResetQuery = 'compass-query-bar/ResetQuery', @@ -136,17 +133,6 @@ export const changeField = ( return { type: QueryBarActions.ChangeField, name, value }; }; -type ChangeSchemaFieldsAction = { - type: QueryBarActions.ChangeSchemaFields; - fields: unknown[]; -}; - -export const changeSchemaFields = ( - fields: unknown[] -): ChangeSchemaFieldsAction => { - return { type: QueryBarActions.ChangeSchemaFields, fields }; -}; - type ApplyQueryAction = { type: QueryBarActions.ApplyQuery; query: BaseQuery; @@ -535,18 +521,6 @@ export const queryBarReducer: Reducer = ( }; } - if ( - isAction( - action, - QueryBarActions.ChangeSchemaFields - ) - ) { - return { - ...state, - schemaFields: action.fields, - }; - } - if ( isAction(action, QueryBarActions.ApplyFromHistory) ) { diff --git a/packages/compass-query-bar/src/stores/query-bar-store.ts b/packages/compass-query-bar/src/stores/query-bar-store.ts index dd4d00be203..22035983a73 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.ts @@ -13,7 +13,6 @@ import { mapQueryToFormFields } from '../utils/query'; import { queryBarReducer, INITIAL_STATE as INITIAL_QUERY_BAR_STATE, - changeSchemaFields, QueryBarActions, updatePreferencesMaxTimeMS, } from './query-bar-reducer'; @@ -162,11 +161,5 @@ export function activatePlugin( } }); - on(globalAppRegistry, 'fields-changed', (fields) => { - if (fields.ns === namespace) { - store.dispatch(changeSchemaFields(fields.autocompleteFields)); - } - }); - return { store, deactivate: cleanup, context: QueryBarStoreContext }; } diff --git a/packages/compass-schema-validation/package.json b/packages/compass-schema-validation/package.json index 8a71fe8c423..3ad9e481dd1 100644 --- a/packages/compass-schema-validation/package.json +++ b/packages/compass-schema-validation/package.json @@ -60,6 +60,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "bson": "^6.2.0", "compass-preferences-model": "^2.17.1", @@ -101,6 +102,7 @@ "@mongodb-js/compass-components": "^1.21.1", "@mongodb-js/compass-crud": "^13.23.1", "@mongodb-js/compass-editor": "^0.20.1", + "@mongodb-js/compass-field-store": "^9.0.14", "@mongodb-js/compass-logging": "^1.2.10", "bson": "^6.2.0", "compass-preferences-model": "^2.17.1", diff --git a/packages/compass-schema-validation/src/components/compass-schema-validation/compass-schema-validation.tsx b/packages/compass-schema-validation/src/components/compass-schema-validation/compass-schema-validation.tsx index 7eba10c16f3..1652a272079 100644 --- a/packages/compass-schema-validation/src/components/compass-schema-validation/compass-schema-validation.tsx +++ b/packages/compass-schema-validation/src/components/compass-schema-validation/compass-schema-validation.tsx @@ -51,8 +51,7 @@ class CompassSchemaValidation extends Component { const mapStateToProps = (state: RootState) => ({ serverVersion: state.serverVersion, validation: state.validation, - fields: state.fields, - namespace: state.namespace, + namespace: state.namespace.ns, isZeroState: state.isZeroState, isLoaded: state.isLoaded, editMode: state.editMode, diff --git a/packages/compass-schema-validation/src/components/validation-editor/validation-editor.spec.tsx b/packages/compass-schema-validation/src/components/validation-editor/validation-editor.spec.tsx index 7817966553f..fc75062041d 100644 --- a/packages/compass-schema-validation/src/components/validation-editor/validation-editor.spec.tsx +++ b/packages/compass-schema-validation/src/components/validation-editor/validation-editor.spec.tsx @@ -16,7 +16,6 @@ describe('ValidationEditor [Component]', function () { const saveValidationSpy = sinon.spy(); const clearSampleDocumentsSpy = sinon.spy(); const serverVersion = '3.6.0'; - const fields: any[] = []; const validation = { validator: '', validationAction: 'warn', @@ -30,6 +29,7 @@ describe('ValidationEditor [Component]', function () { beforeEach(function () { component = mount( @@ -65,7 +64,6 @@ describe('ValidationEditor [Component]', function () { const saveValidationSpy = sinon.spy(); const clearSampleDocumentsSpy = sinon.spy(); const serverVersion = '3.6.0'; - const fields: any[] = []; const validation = { validator: '', validationAction: 'warn', @@ -79,6 +77,7 @@ describe('ValidationEditor [Component]', function () { beforeEach(function () { component = mount( diff --git a/packages/compass-schema-validation/src/components/validation-editor/validation-editor.tsx b/packages/compass-schema-validation/src/components/validation-editor/validation-editor.tsx index fc4c2040097..14b1e2a9881 100644 --- a/packages/compass-schema-validation/src/components/validation-editor/validation-editor.tsx +++ b/packages/compass-schema-validation/src/components/validation-editor/validation-editor.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { debounce } from 'lodash'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import { withLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; @@ -19,6 +18,7 @@ import { CodemirrorMultilineEditor, createValidationAutocompleter, } from '@mongodb-js/compass-editor'; +import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; import type { Validation, @@ -27,9 +27,7 @@ import type { ValidationState, } from '../../modules/validation'; import { checkValidator } from '../../modules/validation'; - import { ActionSelector, LevelSelector } from '../validation-selectors'; -import type { FieldsState } from '../../modules/fields'; const validationEditorStyles = css({ padding: spacing[3], @@ -69,21 +67,20 @@ export type ValidationCodeEditorProps = Pick< React.ComponentProps, 'onChangeText' | 'readOnly' > & { + namespace: string; text: string; - fields: { - name: string; - description?: string; - }[]; serverVersion: string; }; const ValidationCodeEditor = ({ + namespace, text, onChangeText, readOnly, - fields, serverVersion, }: ValidationCodeEditorProps) => { + const fields = useAutocompleteFields(namespace); + const completer = React.useMemo(() => { return createValidationAutocompleter({ fields, serverVersion }); }, [fields, serverVersion]); @@ -99,15 +96,8 @@ const ValidationCodeEditor = ({ ); }; -ValidationCodeEditor.propTypes = { - text: PropTypes.string.isRequired, - onChangeText: PropTypes.func.isRequired, - readOnly: PropTypes.bool.isRequired, - fields: PropTypes.array, - serverVersion: PropTypes.string, -}; - export type ValidationEditorProps = { + namespace: string; clearSampleDocuments: () => void; validatorChanged: (text: string) => void; validationActionChanged: (action: ValidationServerAction) => void; @@ -115,7 +105,6 @@ export type ValidationEditorProps = { cancelValidation: () => void; saveValidation: (text: Validation) => void; serverVersion: string; - fields: FieldsState; validation: Pick< ValidationState, | 'validator' @@ -307,12 +296,12 @@ class ValidationEditor extends Component { )} > { return this.onValidatorChange(text); }} readOnly={!isEditable} - fields={this.props.fields} serverVersion={this.props.serverVersion} />
    diff --git a/packages/compass-schema-validation/src/modules/fields.spec.ts b/packages/compass-schema-validation/src/modules/fields.spec.ts deleted file mode 100644 index df44ebd210d..00000000000 --- a/packages/compass-schema-validation/src/modules/fields.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect } from 'chai'; - -import reducer, { fieldsChanged, FIELDS_CHANGED } from './fields'; - -describe('server version module', function () { - describe('#fieldsChanged', function () { - const fields = { _id: { type: 'ObjectId' }, name: { type: 'String' } }; - - it('returns the FIELDS_CHANGED action', function () { - expect(fieldsChanged(fields)).to.deep.equal({ - type: FIELDS_CHANGED, - fields: fields, - }); - }); - }); - - describe('#reducer', function () { - context('when the action is not fields changed', function () { - it('returns the default state', function () { - expect(reducer(undefined, { type: 'test' } as any)).to.deep.equal([]); - }); - }); - - context('when the action is fields changed', function () { - const fields = { _id: { type: 'ObjectId' }, name: { type: 'String' } }; - - it('returns the new state', function () { - expect(reducer(undefined, fieldsChanged(fields))).to.deep.equal([ - { - name: '_id', - value: '_id', - score: 1, - meta: 'field', - version: '0.0.0', - }, - { - name: 'name', - value: 'name', - score: 1, - meta: 'field', - version: '0.0.0', - }, - ]); - }); - }); - }); -}); diff --git a/packages/compass-schema-validation/src/modules/fields.ts b/packages/compass-schema-validation/src/modules/fields.ts deleted file mode 100644 index 67dc0e8b4db..00000000000 --- a/packages/compass-schema-validation/src/modules/fields.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { RootAction } from '.'; - -/** - * Fields changed action. - */ -export const FIELDS_CHANGED = 'validation/fields/FIELDS_CHANGED' as const; -interface FieldsChangedAction { - type: typeof FIELDS_CHANGED; - fields: Record; -} - -export type FieldsAction = FieldsChangedAction; - -export type FieldsState = { - name: string; - value: string; - score: number; - meta: string; - version: string; -}[]; - -/** - * The initial state. - */ -export const INITIAL_STATE: FieldsState = []; - -/** - * Process the fields into an autocomplete friendly format. - */ -const process = (fields: Record) => - Object.keys(fields).map((key) => { - const field = - key.indexOf('.') > -1 || key.indexOf(' ') > -1 ? `"${key}"` : key; - - return { - name: key, - value: field, - score: 1, - meta: 'field', - version: '0.0.0', - }; - }); - -/** - * Reducer function for handle state changes to fields. - */ -export default function reducer( - state: FieldsState = INITIAL_STATE, - action: RootAction -) { - if (action.type === FIELDS_CHANGED) { - return process(action.fields); - } - - return state; -} - -/** - * Action creator for fields changed events. - */ -export const fieldsChanged = ( - fields: Record -): FieldsChangedAction => ({ - type: FIELDS_CHANGED, - fields, -}); diff --git a/packages/compass-schema-validation/src/modules/index.ts b/packages/compass-schema-validation/src/modules/index.ts index 0daa8f0ef8d..67ec515f8f8 100644 --- a/packages/compass-schema-validation/src/modules/index.ts +++ b/packages/compass-schema-validation/src/modules/index.ts @@ -1,7 +1,5 @@ import type { Action, AnyAction } from 'redux'; import { combineReducers } from 'redux'; -import type { FieldsAction, FieldsState } from './fields'; -import fields, { INITIAL_STATE as FIELDS_INITIAL_STATE } from './fields'; import type { NamespaceAction, NamespaceState } from './namespace'; import namespace, { INITIAL_STATE as NS_INITIAL_STATE } from './namespace'; import type { ServerVersionAction, ServerVersionState } from './server-version'; @@ -38,7 +36,6 @@ interface ResetAction { } export interface RootState { - fields: FieldsState; namespace: NamespaceState; serverVersion: ServerVersionState; validation: ValidationState; @@ -49,7 +46,6 @@ export interface RootState { } export type RootAction = - | FieldsAction | NamespaceAction | ServerVersionAction | ValidationAction @@ -78,7 +74,6 @@ export type SchemaValidationThunkAction< * The intial state of the root reducer. */ export const INITIAL_STATE: RootState = { - fields: FIELDS_INITIAL_STATE, namespace: NS_INITIAL_STATE, serverVersion: SV_INITIAL_STATE, validation: VALIDATION_STATE, @@ -92,7 +87,6 @@ export const INITIAL_STATE: RootState = { * The reducer. */ const appReducer = combineReducers({ - fields, namespace, serverVersion, validation, diff --git a/packages/compass-schema-validation/src/stores/store.spec.ts b/packages/compass-schema-validation/src/stores/store.spec.ts index 65be9a91af2..728bcc3bfa3 100644 --- a/packages/compass-schema-validation/src/stores/store.spec.ts +++ b/packages/compass-schema-validation/src/stores/store.spec.ts @@ -47,7 +47,7 @@ describe('Schema Validation Store', function () { beforeEach(async function () { const activateResult = onActivated( - {} as any, + { namespace: 'test.test' } as any, { globalAppRegistry: globalAppRegistry, dataService: fakeDataService, @@ -72,65 +72,6 @@ describe('Schema Validation Store', function () { expect(store.getState().serverVersion).to.equal('6.0.0'); }); - context('when the validation changes', function () { - it('updates the namespace in the store', function (done) { - const unsubscribe = store.subscribe(() => { - unsubscribe(); - expect(store.getState().fields).to.deep.equal([ - { - name: 'harry', - value: 'harry', - score: 1, - meta: 'field', - version: '0.0.0', - }, - { - name: 'potter', - value: 'potter', - score: 1, - meta: 'field', - version: '0.0.0', - }, - ]); - done(); - }); - - globalAppRegistry.emit('fields-changed', { - fields: { - harry: { - name: 'harry', - path: 'harry', - count: 1, - type: 'Number', - }, - potter: { - name: 'potter', - path: 'potter', - count: 1, - type: 'Boolean', - }, - }, - topLevelFields: ['harry', 'potter'], - autocompleteFields: [ - { - name: 'harry', - value: 'harry', - score: 1, - meta: 'field', - version: '0.0.0', - }, - { - name: 'potter', - value: 'potter', - score: 1, - meta: 'field', - version: '0.0.0', - }, - ], - }); - }); - }); - context('when instance.isWritable changes', function () { it('updates editMode', function () { expect(store.getState().editMode).to.deep.equal({ diff --git a/packages/compass-schema-validation/src/stores/store.ts b/packages/compass-schema-validation/src/stores/store.ts index 9d2759ff9e5..5c235029211 100644 --- a/packages/compass-schema-validation/src/stores/store.ts +++ b/packages/compass-schema-validation/src/stores/store.ts @@ -3,8 +3,6 @@ import thunk from 'redux-thunk'; import type { RootState } from '../modules'; import reducer, { INITIAL_STATE } from '../modules'; import toNS from 'mongodb-ns'; -import { namespaceChanged } from '../modules/namespace'; -import { fieldsChanged } from '../modules/fields'; import { activateValidation } from '../modules/validation'; import { editModeChanged } from '../modules/edit-mode'; import semver from 'semver'; @@ -65,6 +63,7 @@ export function onActivated( ) { const store = configureStore( { + namespace: toNS(options.namespace), serverVersion: instance.build.version, editMode: { collectionTimeSeries: !!options.isTimeSeries, @@ -81,17 +80,8 @@ export function onActivated( } ); - /** - * When the collection is changed, update the store. - */ - on(globalAppRegistry, 'fields-changed', (fields) => { - if (fields.ns === options.namespace) { - store.dispatch(fieldsChanged(fields.fields)); - } - }); - // isWritable can change later - instance.on('change:isWritable', () => { + on(instance, 'change:isWritable', () => { store.dispatch( editModeChanged({ writeStateStoreReadOnly: !instance.isWritable, @@ -99,11 +89,6 @@ export function onActivated( ); }); - if (options.namespace) { - const namespace = toNS(options.namespace); - store.dispatch(namespaceChanged(namespace)); - } - // Activate validation when this plugin is first rendered store.dispatch(activateValidation()); diff --git a/packages/compass-web/src/index.tsx b/packages/compass-web/src/index.tsx index 682860ad9d8..84cd25faa16 100644 --- a/packages/compass-web/src/index.tsx +++ b/packages/compass-web/src/index.tsx @@ -203,59 +203,60 @@ const CompassWeb = ({ - - + -
    - { - return ( - - ); - }} - renderModals={() => { - return ( - <> - - - - - - ); - }} - > -
    -
    -
    - +
    + { + return ( + + ); + }} + renderModals={() => { + return ( + <> + + + + + + ); + }} + > +
    + + +
    diff --git a/packages/hadron-app-registry/src/register-plugin.tsx b/packages/hadron-app-registry/src/register-plugin.tsx index f62091bb235..ebcb4cb20fb 100644 --- a/packages/hadron-app-registry/src/register-plugin.tsx +++ b/packages/hadron-app-registry/src/register-plugin.tsx @@ -369,7 +369,7 @@ export function registerHadronPlugin< useHadronPluginActivate(config, services, props); }, withMockServices( - mocks: Partial>, + mocks: Partial> = {}, options?: Partial> ): React.FunctionComponent { const {