From bd71191bd7ba7d5d3d7e02188fd18322f57a0b24 Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Fri, 28 Jun 2024 11:33:55 -0400 Subject: [PATCH 01/13] initial working query history autocompleter --- packages/compass-components/src/index.ts | 1 + .../query-autocompleter-with-history.ts | 26 +++++ .../codemirror/query-history-autocompleter.ts | 105 ++++++++++++++++++ packages/compass-editor/src/editor.tsx | 12 ++ packages/compass-editor/src/index.ts | 2 + .../src/components/option-editor.tsx | 43 ++++--- packages/compass-query-bar/src/index.tsx | 4 +- 7 files changed, 179 insertions(+), 14 deletions(-) create mode 100644 packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts create mode 100644 packages/compass-editor/src/codemirror/query-history-autocompleter.ts diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 1280ca68cce..df7495c3d1c 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -196,3 +196,4 @@ export { RequiredURLSearchParamsProvider, useRequiredURLSearchParams, } from './components/links/link'; +export { formatDate } from './utils/format-date'; diff --git a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts new file mode 100644 index 00000000000..b80f60a9f69 --- /dev/null +++ b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts @@ -0,0 +1,26 @@ +import { createQueryHistoryAutocompleter } from './query-history-autocompleter'; +import { createQueryAutocompleter } from './query-autocompleter'; +import type { RecentQuery } from '@mongodb-js/my-queries-storage'; +import type { + CompletionSource, + CompletionContext, +} from '@codemirror/autocomplete'; +import type { CompletionOptions } from '../autocompleter'; + +export const createFullQueryAutocompleter = ( + recentQueries: RecentQuery[], + options: Pick = {}, + onApply: (query: RecentQuery) => void +): CompletionSource => { + const queryHistoryAutocompleter = createQueryHistoryAutocompleter( + recentQueries, + onApply + ); + const originalQueryAutocompleter = createQueryAutocompleter(options); + + return function fullCompletions(context: CompletionContext) { + if (context.state.doc.toString() !== '{}') + return originalQueryAutocompleter(context); + return queryHistoryAutocompleter(context); + }; +}; diff --git a/packages/compass-editor/src/codemirror/query-history-autocompleter.ts b/packages/compass-editor/src/codemirror/query-history-autocompleter.ts new file mode 100644 index 00000000000..9f96b7031b5 --- /dev/null +++ b/packages/compass-editor/src/codemirror/query-history-autocompleter.ts @@ -0,0 +1,105 @@ +import type { + CompletionContext, + CompletionSource, +} from '@codemirror/autocomplete'; +import { formatDate, spacing } from '@mongodb-js/compass-components'; +import type { RecentQuery } from '@mongodb-js/my-queries-storage'; +import { toJSString } from 'mongodb-query-parser'; +import { css } from '@mongodb-js/compass-components'; + +export const createQueryHistoryAutocompleter = ( + recentQueries: RecentQuery[], + onApply: (query: RecentQuery) => void +): CompletionSource => { + return function queryCompletions(context: CompletionContext) { + if (recentQueries.length === 0) { + return null; + } + + const queryLabelStyles = css({ + textTransform: 'capitalize', + fontWeight: 'bold', + margin: `${spacing[2]}px 0`, + }); + + const queryCodeStyles = css({ + maxHeight: '30vh', + }); + + const properties = [ + 'filter', + 'sort', + 'collation', + 'sort', + 'hint', + 'skip', + 'limit', + 'maxTimeMS', + ]; + + function createInfo(query: RecentQuery): { + dom: Node; + destroy?: () => void; + } { + const container = document.createElement('div'); + container.setAttribute('data-testid', 'query-history-query-attribute'); + Object.entries(query).forEach(([key, value]) => { + if (properties.includes(key)) { + const formattedQuery = toJSString(value); + const codeDiv = document.createElement('div'); + + const label = document.createElement('label'); + label.setAttribute('data-testid', 'query-history-query-label'); + label.className = queryLabelStyles; + label.textContent = key; + + const code = document.createElement('pre'); + code.setAttribute('data-testid', 'query-history-query-code'); + code.className = queryCodeStyles; + if (formattedQuery) code.textContent = formattedQuery; + + codeDiv.append(label); + codeDiv.appendChild(code); + container.appendChild(codeDiv); + } + }); + + return { + dom: container, + destroy: () => { + while (container.firstChild) { + container.removeChild(container.firstChild); + } + }, + }; + } + + function createQuery(query: RecentQuery): string { + let res = ''; + Object.entries(query).forEach(([key, value]) => { + if (properties.includes(key)) { + const formattedQuery = toJSString(value); + const noFilterKey = key === 'filter' ? '' : `${key}: `; + res += formattedQuery ? `, ${noFilterKey}${formattedQuery}` : ''; + } + }); + const len = res.length; + return len <= 100 ? res.slice(2, res.length) : res.slice(2, 100); + } + const options = recentQueries.map((query) => ({ + label: createQuery(query), + type: 'text', + detail: formatDate(query._lastExecuted.getTime()), + info: () => createInfo(query).dom, + apply: () => { + onApply(query); + }, + boost: query._lastExecuted.getTime(), + })); + + return { + from: context.pos, + options: options, + }; + }; +}; diff --git a/packages/compass-editor/src/editor.tsx b/packages/compass-editor/src/editor.tsx index c9279457371..f3b6db33853 100644 --- a/packages/compass-editor/src/editor.tsx +++ b/packages/compass-editor/src/editor.tsx @@ -47,6 +47,7 @@ import { closeBrackets, closeBracketsKeymap, snippetCompletion, + startCompletion, } from '@codemirror/autocomplete'; import type { IconGlyph } from '@mongodb-js/compass-components'; import { @@ -341,6 +342,7 @@ function getStylesForTheme(theme: CodemirrorThemeType) { }, '& .cm-tooltip .completion-info p': { margin: 0, + marginRight: '10em', marginTop: `${spacing[2]}px`, marginBottom: `${spacing[2]}px`, }, @@ -601,6 +603,7 @@ export type EditorRef = { prettify: () => boolean; applySnippet: (template: string) => boolean; focus: () => boolean; + startCompletion: () => boolean; readonly editorContents: string | null; readonly editor: EditorView | null; }; @@ -710,6 +713,12 @@ const BaseEditor = React.forwardRef(function BaseEditor( editorViewRef.current.focus(); return true; }, + startCompletion() { + if (!editorViewRef.current) { + return false; + } + return startCompletion(editorViewRef.current); + }, get editorContents() { if (!editorViewRef.current) { return null; @@ -1350,6 +1359,9 @@ const MultilineEditor = React.forwardRef( applySnippet(template: string) { return editorRef.current?.applySnippet(template) ?? false; }, + startCompletion() { + return editorRef.current?.startCompletion() ?? false; + }, get editorContents() { return editorRef.current?.editorContents ?? null; }, diff --git a/packages/compass-editor/src/index.ts b/packages/compass-editor/src/index.ts index e06ec89ab65..3248c6e43a9 100644 --- a/packages/compass-editor/src/index.ts +++ b/packages/compass-editor/src/index.ts @@ -21,3 +21,5 @@ export { createQueryAutocompleter } from './codemirror/query-autocompleter'; export { createStageAutocompleter } from './codemirror/stage-autocompleter'; export { createAggregationAutocompleter } from './codemirror/aggregation-autocompleter'; export { createSearchIndexAutocompleter } from './codemirror/search-index-autocompleter'; +export { createQueryHistoryAutocompleter } from './codemirror/query-history-autocompleter'; +export { createFullQueryAutocompleter } from './codemirror/query-autocompleter-with-history'; diff --git a/packages/compass-query-bar/src/components/option-editor.tsx b/packages/compass-query-bar/src/components/option-editor.tsx index 2512b99f513..b38d78d4474 100644 --- a/packages/compass-query-bar/src/components/option-editor.tsx +++ b/packages/compass-query-bar/src/components/option-editor.tsx @@ -12,13 +12,15 @@ import { import type { Command, EditorRef } from '@mongodb-js/compass-editor'; import { CodemirrorInlineEditor as InlineEditor, - createQueryAutocompleter, + createFullQueryAutocompleter, } from '@mongodb-js/compass-editor'; import { connect } from '../stores/context'; import { usePreference } from 'compass-preferences-model/provider'; import { lenientlyFixQuery } from '../query/leniently-fix-query'; import type { RootState } from '../stores/query-bar-store'; import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; +import type { RecentQuery } from '@mongodb-js/my-queries-storage'; +import { applyFromHistory } from '@mongodb-js/compass-query-bar'; const editorContainerStyles = css({ position: 'relative', @@ -94,6 +96,8 @@ type OptionEditorProps = { ['data-testid']?: string; insights?: Signal | Signal[]; disabled?: boolean; + recentQueries: RecentQuery[]; + onApplyQuery: (query: RecentQuery) => void; }; export const OptionEditor: React.FunctionComponent = ({ @@ -110,6 +114,8 @@ export const OptionEditor: React.FunctionComponent = ({ ['data-testid']: dataTestId, insights, disabled = false, + recentQueries, + onApplyQuery, }) => { const showInsights = usePreference('showInsights'); const editorContainerRef = useRef(null); @@ -140,11 +146,15 @@ export const OptionEditor: React.FunctionComponent = ({ const schemaFields = useAutocompleteFields(namespace); const completer = useMemo(() => { - return createQueryAutocompleter({ - fields: schemaFields, - serverVersion, - }); - }, [schemaFields, serverVersion]); + return createFullQueryAutocompleter( + recentQueries, + { + fields: schemaFields, + serverVersion, + }, + onApplyQuery + ); + }, [recentQueries, schemaFields, serverVersion, onApplyQuery]); const onFocus = () => { if (insertEmptyDocOnFocus) { @@ -152,6 +162,7 @@ export const OptionEditor: React.FunctionComponent = ({ if (editorRef.current?.editorContents === '') { editorRef.current?.applySnippet('\\{${}}'); } + if (editorRef.current?.editor) editorRef.current?.startCompletion(); }); } }; @@ -215,11 +226,17 @@ export const OptionEditor: React.FunctionComponent = ({ ); }; -const ConnectedOptionEditor = connect((state: RootState) => { - return { - namespace: state.queryBar.namespace, - serverVersion: state.queryBar.serverVersion, - }; -})(OptionEditor); +const ConnectedOptionEditor = (state: RootState) => ({ + namespace: state.queryBar.namespace, + serverVersion: state.queryBar.serverVersion, + recentQueries: [ + ...state.queryBar.recentQueries, + ...state.queryBar.favoriteQueries, + ], +}); + +const ConnectedOnApply = { + onApplyQuery: applyFromHistory, +}; -export default ConnectedOptionEditor; +export default connect(ConnectedOptionEditor, ConnectedOnApply)(OptionEditor); diff --git a/packages/compass-query-bar/src/index.tsx b/packages/compass-query-bar/src/index.tsx index 393b78dda46..2c4aeab6e36 100644 --- a/packages/compass-query-bar/src/index.tsx +++ b/packages/compass-query-bar/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { registerHadronPlugin } from 'hadron-app-registry'; -import { activatePlugin } from './stores/query-bar-store'; +import { activatePlugin } from './stores/query-bar-store'; // changed import { dataServiceLocator, type DataServiceLocator, @@ -78,3 +78,5 @@ export { }; export type { QueryBarService }; export type { BaseQuery as Query } from './constants/query-properties'; +export { applyFromHistory } from './stores/query-bar-reducer'; +export type { RootState } from './stores/query-bar-store'; From 4292e9a0d92982c646048daf41ec44933361075a Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Mon, 1 Jul 2024 10:53:29 -0400 Subject: [PATCH 02/13] refactoring code, update lastExecuted when saveRecentQuery is called --- .../query-autocompleter-with-history.ts | 14 +- .../codemirror/query-history-autocompleter.ts | 145 ++++++++---------- packages/compass-editor/src/editor.tsx | 2 +- packages/compass-editor/src/index.ts | 2 +- .../src/components/option-editor.tsx | 39 +++-- .../src/components/query-bar.tsx | 6 +- packages/compass-query-bar/src/index.tsx | 6 +- .../src/stores/query-bar-reducer.ts | 2 + 8 files changed, 114 insertions(+), 102 deletions(-) diff --git a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts index b80f60a9f69..cdfa2009e77 100644 --- a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts +++ b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts @@ -1,21 +1,25 @@ -import { createQueryHistoryAutocompleter } from './query-history-autocompleter'; +import { + type SavedQuery, + createQueryHistoryAutocompleter, +} from './query-history-autocompleter'; import { createQueryAutocompleter } from './query-autocompleter'; -import type { RecentQuery } from '@mongodb-js/my-queries-storage'; + import type { CompletionSource, CompletionContext, } from '@codemirror/autocomplete'; import type { CompletionOptions } from '../autocompleter'; -export const createFullQueryAutocompleter = ( - recentQueries: RecentQuery[], +export const createQueryWithHistoryAutocompleter = ( + recentQueries: SavedQuery[], options: Pick = {}, - onApply: (query: RecentQuery) => void + onApply: (query: SavedQuery['queryProperties']) => void ): CompletionSource => { const queryHistoryAutocompleter = createQueryHistoryAutocompleter( recentQueries, onApply ); + const originalQueryAutocompleter = createQueryAutocompleter(options); return function fullCompletions(context: CompletionContext) { diff --git a/packages/compass-editor/src/codemirror/query-history-autocompleter.ts b/packages/compass-editor/src/codemirror/query-history-autocompleter.ts index 9f96b7031b5..3a7e5dee48c 100644 --- a/packages/compass-editor/src/codemirror/query-history-autocompleter.ts +++ b/packages/compass-editor/src/codemirror/query-history-autocompleter.ts @@ -3,98 +3,34 @@ import type { CompletionSource, } from '@codemirror/autocomplete'; import { formatDate, spacing } from '@mongodb-js/compass-components'; -import type { RecentQuery } from '@mongodb-js/my-queries-storage'; import { toJSString } from 'mongodb-query-parser'; import { css } from '@mongodb-js/compass-components'; +export type SavedQuery = { + lastExecuted: Date; + queryProperties: { + [properyName: string]: any; + }; +}; + export const createQueryHistoryAutocompleter = ( - recentQueries: RecentQuery[], - onApply: (query: RecentQuery) => void + savedQueries: SavedQuery[], + onApply: (query: SavedQuery['queryProperties']) => void ): CompletionSource => { return function queryCompletions(context: CompletionContext) { - if (recentQueries.length === 0) { + if (savedQueries.length === 0) { return null; } - const queryLabelStyles = css({ - textTransform: 'capitalize', - fontWeight: 'bold', - margin: `${spacing[2]}px 0`, - }); - - const queryCodeStyles = css({ - maxHeight: '30vh', - }); - - const properties = [ - 'filter', - 'sort', - 'collation', - 'sort', - 'hint', - 'skip', - 'limit', - 'maxTimeMS', - ]; - - function createInfo(query: RecentQuery): { - dom: Node; - destroy?: () => void; - } { - const container = document.createElement('div'); - container.setAttribute('data-testid', 'query-history-query-attribute'); - Object.entries(query).forEach(([key, value]) => { - if (properties.includes(key)) { - const formattedQuery = toJSString(value); - const codeDiv = document.createElement('div'); - - const label = document.createElement('label'); - label.setAttribute('data-testid', 'query-history-query-label'); - label.className = queryLabelStyles; - label.textContent = key; - - const code = document.createElement('pre'); - code.setAttribute('data-testid', 'query-history-query-code'); - code.className = queryCodeStyles; - if (formattedQuery) code.textContent = formattedQuery; - - codeDiv.append(label); - codeDiv.appendChild(code); - container.appendChild(codeDiv); - } - }); - - return { - dom: container, - destroy: () => { - while (container.firstChild) { - container.removeChild(container.firstChild); - } - }, - }; - } - - function createQuery(query: RecentQuery): string { - let res = ''; - Object.entries(query).forEach(([key, value]) => { - if (properties.includes(key)) { - const formattedQuery = toJSString(value); - const noFilterKey = key === 'filter' ? '' : `${key}: `; - res += formattedQuery ? `, ${noFilterKey}${formattedQuery}` : ''; - } - }); - const len = res.length; - return len <= 100 ? res.slice(2, res.length) : res.slice(2, 100); - } - const options = recentQueries.map((query) => ({ + const options = savedQueries.map((query) => ({ label: createQuery(query), type: 'text', - detail: formatDate(query._lastExecuted.getTime()), + detail: formatDate(query.lastExecuted.getTime()), info: () => createInfo(query).dom, apply: () => { - onApply(query); + onApply(query.queryProperties); }, - boost: query._lastExecuted.getTime(), + boost: query.lastExecuted.getTime(), })); return { @@ -103,3 +39,56 @@ export const createQueryHistoryAutocompleter = ( }; }; }; + +const queryLabelStyles = css({ + textTransform: 'capitalize', + fontWeight: 'bold', + margin: `${spacing[2]}px 0`, +}); + +const queryCodeStyles = css({ + maxHeight: '30vh', +}); + +function createQuery(query: SavedQuery): string { + let res = ''; + Object.entries(query.queryProperties).forEach(([key, value]) => { + const formattedQuery = toJSString(value); + const noFilterKey = key === 'filter' ? '' : `${key}: `; + res += formattedQuery ? `, ${noFilterKey}${formattedQuery}` : ''; + }); + const len = res.length; + return len <= 100 ? res.slice(2, res.length) : res.slice(2, 100); +} + +function createInfo(query: SavedQuery): { + dom: Node; + destroy?: () => void; +} { + const container = document.createElement('div'); + Object.entries(query.queryProperties).forEach(([key, value]) => { + const formattedQuery = toJSString(value); + const codeDiv = document.createElement('div'); + + const label = document.createElement('label'); + label.className = queryLabelStyles; + label.textContent = key; + + const code = document.createElement('pre'); + code.className = queryCodeStyles; + if (formattedQuery) code.textContent = formattedQuery; + + codeDiv.append(label); + codeDiv.appendChild(code); + container.appendChild(codeDiv); + }); + + return { + dom: container, + destroy: () => { + while (container.firstChild) { + container.removeChild(container.firstChild); + } + }, + }; +} diff --git a/packages/compass-editor/src/editor.tsx b/packages/compass-editor/src/editor.tsx index f3b6db33853..c7ff66d396c 100644 --- a/packages/compass-editor/src/editor.tsx +++ b/packages/compass-editor/src/editor.tsx @@ -342,7 +342,7 @@ function getStylesForTheme(theme: CodemirrorThemeType) { }, '& .cm-tooltip .completion-info p': { margin: 0, - marginRight: '10em', + marginRight: `${spacing[2]}px`, marginTop: `${spacing[2]}px`, marginBottom: `${spacing[2]}px`, }, diff --git a/packages/compass-editor/src/index.ts b/packages/compass-editor/src/index.ts index 3248c6e43a9..70024a73d80 100644 --- a/packages/compass-editor/src/index.ts +++ b/packages/compass-editor/src/index.ts @@ -22,4 +22,4 @@ export { createStageAutocompleter } from './codemirror/stage-autocompleter'; export { createAggregationAutocompleter } from './codemirror/aggregation-autocompleter'; export { createSearchIndexAutocompleter } from './codemirror/search-index-autocompleter'; export { createQueryHistoryAutocompleter } from './codemirror/query-history-autocompleter'; -export { createFullQueryAutocompleter } from './codemirror/query-autocompleter-with-history'; +export { createQueryWithHistoryAutocompleter } from './codemirror/query-autocompleter-with-history'; diff --git a/packages/compass-query-bar/src/components/option-editor.tsx b/packages/compass-query-bar/src/components/option-editor.tsx index b38d78d4474..06c295357af 100644 --- a/packages/compass-query-bar/src/components/option-editor.tsx +++ b/packages/compass-query-bar/src/components/option-editor.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import type { Signal } from '@mongodb-js/compass-components'; import { css, @@ -12,7 +12,7 @@ import { import type { Command, EditorRef } from '@mongodb-js/compass-editor'; import { CodemirrorInlineEditor as InlineEditor, - createFullQueryAutocompleter, + createQueryWithHistoryAutocompleter, } from '@mongodb-js/compass-editor'; import { connect } from '../stores/context'; import { usePreference } from 'compass-preferences-model/provider'; @@ -20,7 +20,12 @@ import { lenientlyFixQuery } from '../query/leniently-fix-query'; import type { RootState } from '../stores/query-bar-store'; import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; import type { RecentQuery } from '@mongodb-js/my-queries-storage'; -import { applyFromHistory } from '@mongodb-js/compass-query-bar'; +import { + applyFromHistory, + fetchSavedQueries, +} from '../stores/query-bar-reducer'; +import { getQueryAttributes } from '../utils'; +import type { BaseQuery } from '../constants/query-properties'; const editorContainerStyles = css({ position: 'relative', @@ -96,8 +101,9 @@ type OptionEditorProps = { ['data-testid']?: string; insights?: Signal | Signal[]; disabled?: boolean; - recentQueries: RecentQuery[]; - onApplyQuery: (query: RecentQuery) => void; + savedQueries: RecentQuery[]; + onApplyQuery: (query: BaseQuery) => void; + loadSavedQueries: () => void; }; export const OptionEditor: React.FunctionComponent = ({ @@ -114,9 +120,14 @@ export const OptionEditor: React.FunctionComponent = ({ ['data-testid']: dataTestId, insights, disabled = false, - recentQueries, + savedQueries, onApplyQuery, + loadSavedQueries, }) => { + useEffect(() => { + loadSavedQueries(); + }, [loadSavedQueries]); + const showInsights = usePreference('showInsights'); const editorContainerRef = useRef(null); const editorRef = useRef(null); @@ -146,15 +157,18 @@ export const OptionEditor: React.FunctionComponent = ({ const schemaFields = useAutocompleteFields(namespace); const completer = useMemo(() => { - return createFullQueryAutocompleter( - recentQueries, + return createQueryWithHistoryAutocompleter( + savedQueries.map((query) => ({ + lastExecuted: query._lastExecuted, + queryProperties: getQueryAttributes(query), + })), { fields: schemaFields, serverVersion, }, onApplyQuery ); - }, [recentQueries, schemaFields, serverVersion, onApplyQuery]); + }, [savedQueries, schemaFields, serverVersion, onApplyQuery]); const onFocus = () => { if (insertEmptyDocOnFocus) { @@ -229,14 +243,15 @@ export const OptionEditor: React.FunctionComponent = ({ const ConnectedOptionEditor = (state: RootState) => ({ namespace: state.queryBar.namespace, serverVersion: state.queryBar.serverVersion, - recentQueries: [ + savedQueries: [ ...state.queryBar.recentQueries, ...state.queryBar.favoriteQueries, ], }); -const ConnectedOnApply = { +const mapDispatchToProps = { onApplyQuery: applyFromHistory, + loadSavedQueries: fetchSavedQueries, }; -export default connect(ConnectedOptionEditor, ConnectedOnApply)(OptionEditor); +export default connect(ConnectedOptionEditor, mapDispatchToProps)(OptionEditor); diff --git a/packages/compass-query-bar/src/components/query-bar.tsx b/packages/compass-query-bar/src/components/query-bar.tsx index d2383c88092..dd6a2a528ca 100644 --- a/packages/compass-query-bar/src/components/query-bar.tsx +++ b/packages/compass-query-bar/src/components/query-bar.tsx @@ -30,7 +30,10 @@ import { resetQuery, explainQuery, } from '../stores/query-bar-reducer'; -import { toggleQueryOptions } from '../stores/query-bar-reducer'; +import { + toggleQueryOptions, + fetchSavedQueries, +} from '../stores/query-bar-reducer'; import { isEqualDefaultQuery, isQueryValid } from '../utils/query'; import type { QueryProperty } from '../constants/query-properties'; import { QueryAI } from './query-ai'; @@ -350,6 +353,7 @@ export default connect( if (applied === false) { return; } + dispatch(fetchSavedQueries()); ownProps.onApply?.(applied); }, onReset: () => { diff --git a/packages/compass-query-bar/src/index.tsx b/packages/compass-query-bar/src/index.tsx index 2c4aeab6e36..1f02d896140 100644 --- a/packages/compass-query-bar/src/index.tsx +++ b/packages/compass-query-bar/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { registerHadronPlugin } from 'hadron-app-registry'; -import { activatePlugin } from './stores/query-bar-store'; // changed +import { activatePlugin } from './stores/query-bar-store'; import { dataServiceLocator, type DataServiceLocator, @@ -77,6 +77,4 @@ export { queryBarServiceLocator, }; export type { QueryBarService }; -export type { BaseQuery as Query } from './constants/query-properties'; -export { applyFromHistory } from './stores/query-bar-reducer'; -export type { RootState } from './stores/query-bar-store'; +export { type BaseQuery as Query } from './constants/query-properties'; 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 71da0837305..0f56bc85cc4 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -159,6 +159,7 @@ export const resetQuery = ( DEFAULT_FIELD_VALUES ); dispatch({ type: QueryBarActions.ResetQuery, fields, source }); + dispatch(fetchSavedQueries); return cloneDeep(DEFAULT_QUERY_VALUES); }; }; @@ -432,6 +433,7 @@ const saveRecentQuery = ( _ns: namespace, _host: host ?? '', }); + _dispatch(fetchSavedQueries()); } catch (e) { debug('Failed to save recent query', e); } From e9771123fd0d2661b8e76ca5d4d88715e1e3c726 Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Tue, 2 Jul 2024 12:44:03 -0400 Subject: [PATCH 03/13] added some initial tests --- .../query-autocompleter-with-history.test.ts | 95 +++++++++++++++++++ .../src/components/option-editor.spec.tsx | 63 ++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts diff --git a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts new file mode 100644 index 00000000000..34c8ef06683 --- /dev/null +++ b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts @@ -0,0 +1,95 @@ +import { expect } from 'chai'; +import { createQueryWithHistoryAutocompleter } from './query-autocompleter-with-history'; +import { setupCodemirrorCompleter } from '../../test/completer'; +import type { SavedQuery } from '../../dist/codemirror/query-history-autocompleter'; +import applyFromHistory from '@mongodb-js/compass-query-bar'; +// import Sinon from 'sinon'; +// import type { Completion } from '@codemirror/autocomplete'; + +describe('query history autocompleter', function () { + const { getCompletions, cleanup } = setupCodemirrorCompleter( + createQueryWithHistoryAutocompleter + ); + + const savedQueries: SavedQuery[] = [ + { + lastExecuted: new Date('2023-06-01T12:00:00Z'), + queryProperties: { + filter: { status: 'active' }, + }, + }, + { + lastExecuted: new Date('2023-06-02T14:00:00Z'), + queryProperties: { + filter: { age: { $gt: 30 } }, + project: { name: 1, age: 1, address: 1 }, + collation: { locale: 'en' }, + sort: { age: 1 }, + skip: 5, + limit: 100, + maxTimeMS: 3000, + }, + }, + { + lastExecuted: new Date('2023-06-03T16:00:00Z'), + queryProperties: { + filter: { score: { $gte: 85 } }, + project: { studentName: 1, score: 1 }, + sort: { score: -1 }, + hint: { indexName: 'score_1' }, + limit: 20, + maxTimeMS: 1000, + }, + }, + { + lastExecuted: new Date('2023-06-04T18:00:00Z'), + queryProperties: { + filter: { isActive: true }, + project: { userId: 1, isActive: 1 }, + collation: { locale: 'simple' }, + sort: { userId: 1 }, + limit: 10, + maxTimeMS: 500, + }, + }, + { + lastExecuted: new Date('2023-06-05T20:00:00Z'), + queryProperties: { + filter: { category: 'electronics' }, + project: { productId: 1, category: 1, price: 1 }, + sort: { price: -1 }, + limit: 30, + maxTimeMS: 1500, + }, + }, + ]; + + after(cleanup); + + // eslint-disable-next-line @typescript-eslint/ban-types + const mockOnApply: (query: SavedQuery['queryProperties']) => void = + applyFromHistory; + + it('returns all saved queries as completions on click', function () { + expect( + getCompletions('{}', savedQueries, undefined, mockOnApply) + ).to.have.lengthOf(5); + }); + + it('returns normal autocompletion when user starts typing', function () { + expect( + getCompletions('foo', savedQueries, undefined, mockOnApply) + ).to.have.lengthOf(45); + }); + + it('completes "any text" when inside a string', function () { + expect( + getCompletions( + '{ bar: 1, buz: 2, foo: "b', + savedQueries, + undefined, + mockOnApply + ).map((completion) => completion.label) + ).to.deep.eq(['bar', '1', 'buz', '2', 'foo']); + }); +}); 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 f59d8230eab..2362126d920 100644 --- a/packages/compass-query-bar/src/components/option-editor.spec.tsx +++ b/packages/compass-query-bar/src/components/option-editor.spec.tsx @@ -3,6 +3,11 @@ import { expect } from 'chai'; import { cleanup, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { OptionEditor } from './option-editor'; +import Sinon from 'sinon'; +import { + applyFromHistory, + fetchSavedQueries, +} from '../stores/query-bar-reducer'; class MockPasteEvent extends window.Event { constructor(private text: string) { @@ -35,6 +40,9 @@ describe('OptionEditor', function () { insertEmptyDocOnFocus onChange={() => {}} value="" + savedQueries={[]} + onApplyQuery={applyFromHistory} + loadSavedQueries={fetchSavedQueries} > ); @@ -54,6 +62,9 @@ describe('OptionEditor', function () { insertEmptyDocOnFocus onChange={() => {}} value="{ foo: 1 }" + savedQueries={[]} + onApplyQuery={applyFromHistory} + loadSavedQueries={fetchSavedQueries} > ); @@ -73,6 +84,9 @@ describe('OptionEditor', function () { insertEmptyDocOnFocus onChange={() => {}} value="" + savedQueries={[]} + onApplyQuery={applyFromHistory} + loadSavedQueries={fetchSavedQueries} > ); @@ -98,6 +112,9 @@ describe('OptionEditor', function () { insertEmptyDocOnFocus onChange={() => {}} value="" + savedQueries={[]} + onApplyQuery={applyFromHistory} + loadSavedQueries={fetchSavedQueries} > ); @@ -125,6 +142,9 @@ describe('OptionEditor', function () { insertEmptyDocOnFocus onChange={() => {}} value="" + savedQueries={[]} + onApplyQuery={applyFromHistory} + loadSavedQueries={fetchSavedQueries} > ); @@ -140,4 +160,47 @@ describe('OptionEditor', function () { }); }); }); + + describe('createQueryWithHistoryAutocompleter', function () { + const onApplySpy = Sinon.spy(); + + it.only('calls onApply when an autocomplete option is selected', async function () { + render( + {}} + value="" + savedQueries={[ + { + filter: { a: 1 }, + _lastExecuted: new Date('2024-06-01T12:00:00Z'), + }, + { + filter: { a: 2 }, + _lastExecuted: new Date('2023-06-02T12:00:00Z'), + }, + ]} + onApplyQuery={applyFromHistory} + loadSavedQueries={fetchSavedQueries} + > + ); + + userEvent.click(screen.getByRole('textbox')); + await waitFor(() => { + expect(screen.getAllByText('{ a: 1 }')[0]).to.be.visible; + expect(screen.getAllByText('{ a: 1 }')[1]).to.be.visible; + expect(screen.getByText('{ a: 2 }')).to.be.visible; + }); + + // Simulate selecting the autocomplete option + userEvent.click(screen.getByText('{ a: 2 }')); + // await waitFor(() => { + // expect(screen.getByRole('textbox').textContent).to.eq('{ a: 2 }'); + // }); + await waitFor(() => { + expect(onApplySpy).to.have.been.called; + }); + }); + }); }); From af5459bba8eb285adcc7a495f4f9b2eb72a4b98d Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Tue, 2 Jul 2024 15:23:02 -0400 Subject: [PATCH 04/13] fixed option-editor test --- .../query-autocompleter-with-history.test.ts | 2 -- .../src/components/option-editor.spec.tsx | 36 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts index 34c8ef06683..dd3bd6a5f63 100644 --- a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts +++ b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts @@ -3,8 +3,6 @@ import { createQueryWithHistoryAutocompleter } from './query-autocompleter-with- import { setupCodemirrorCompleter } from '../../test/completer'; import type { SavedQuery } from '../../dist/codemirror/query-history-autocompleter'; import applyFromHistory from '@mongodb-js/compass-query-bar'; -// import Sinon from 'sinon'; -// import type { Completion } from '@codemirror/autocomplete'; describe('query history autocompleter', function () { const { getCompletions, cleanup } = setupCodemirrorCompleter( 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 2362126d920..23784ee8560 100644 --- a/packages/compass-query-bar/src/components/option-editor.spec.tsx +++ b/packages/compass-query-bar/src/components/option-editor.spec.tsx @@ -3,11 +3,12 @@ import { expect } from 'chai'; import { cleanup, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { OptionEditor } from './option-editor'; -import Sinon from 'sinon'; +import type { SinonSpy } from 'sinon'; import { applyFromHistory, fetchSavedQueries, } from '../stores/query-bar-reducer'; +import sinon from 'sinon'; class MockPasteEvent extends window.Event { constructor(private text: string) { @@ -162,9 +163,16 @@ describe('OptionEditor', function () { }); describe('createQueryWithHistoryAutocompleter', function () { - const onApplySpy = Sinon.spy(); + let onApplySpy: SinonSpy; + let loadSavedQueriesSpy: SinonSpy; - it.only('calls onApply when an autocomplete option is selected', async function () { + afterEach(function () { + cleanup(); + }); + + it('filter applied correctly when autocomplete option is clicked', async function () { + onApplySpy = sinon.spy(); + loadSavedQueriesSpy = sinon.spy(); render( ); @@ -190,16 +199,17 @@ describe('OptionEditor', function () { await waitFor(() => { expect(screen.getAllByText('{ a: 1 }')[0]).to.be.visible; expect(screen.getAllByText('{ a: 1 }')[1]).to.be.visible; - expect(screen.getByText('{ a: 2 }')).to.be.visible; + expect(screen.getByText('{ a: 2 }, sort: { a: -1 }')).to.be.visible; }); // Simulate selecting the autocomplete option - userEvent.click(screen.getByText('{ a: 2 }')); - // await waitFor(() => { - // expect(screen.getByRole('textbox').textContent).to.eq('{ a: 2 }'); - // }); + userEvent.click(screen.getByText('{ a: 2 }, sort: { a: -1 }')); await waitFor(() => { - expect(onApplySpy).to.have.been.called; + expect(onApplySpy.lastCall).to.be.calledWithExactly({ + filter: { a: 2 }, + sort: { a: -1 }, + }); + expect(loadSavedQueriesSpy).to.be.called; }); }); }); From dd05067b6e5e266ffcad6cc44f994065841d7a9d Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Tue, 2 Jul 2024 16:24:55 -0400 Subject: [PATCH 05/13] removing unnecessary dependencies --- .../src/codemirror/query-autocompleter-with-history.test.ts | 5 +---- packages/compass-query-bar/src/stores/query-bar-reducer.ts | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts index dd3bd6a5f63..47b2d0dda73 100644 --- a/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts +++ b/packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts @@ -2,7 +2,6 @@ import { expect } from 'chai'; import { createQueryWithHistoryAutocompleter } from './query-autocompleter-with-history'; import { setupCodemirrorCompleter } from '../../test/completer'; import type { SavedQuery } from '../../dist/codemirror/query-history-autocompleter'; -import applyFromHistory from '@mongodb-js/compass-query-bar'; describe('query history autocompleter', function () { const { getCompletions, cleanup } = setupCodemirrorCompleter( @@ -64,9 +63,7 @@ describe('query history autocompleter', function () { after(cleanup); - // eslint-disable-next-line @typescript-eslint/ban-types - const mockOnApply: (query: SavedQuery['queryProperties']) => void = - applyFromHistory; + const mockOnApply: (query: SavedQuery['queryProperties']) => any = () => {}; it('returns all saved queries as completions on click', function () { expect( 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 0f56bc85cc4..bd64aadb36d 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -159,7 +159,6 @@ export const resetQuery = ( DEFAULT_FIELD_VALUES ); dispatch({ type: QueryBarActions.ResetQuery, fields, source }); - dispatch(fetchSavedQueries); return cloneDeep(DEFAULT_QUERY_VALUES); }; }; From 696d97399a2eb2b29fbe469555f9b062ede3cc8a Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Wed, 3 Jul 2024 09:51:28 -0400 Subject: [PATCH 06/13] added dispatch fetchSavedQueries to update existing queries attributes --- packages/compass-query-bar/src/stores/query-bar-reducer.ts | 1 + 1 file changed, 1 insertion(+) 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 bd64aadb36d..fe0737cf76a 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -424,6 +424,7 @@ const saveRecentQuery = ( existingQuery._id, updateAttributes ); + _dispatch(fetchSavedQueries()); return; } From d866e553f92b999960fddd0409027949c39cd115 Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Wed, 3 Jul 2024 10:42:52 -0400 Subject: [PATCH 07/13] undo commit --- packages/compass-query-bar/src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-query-bar/src/index.tsx b/packages/compass-query-bar/src/index.tsx index 1f02d896140..393b78dda46 100644 --- a/packages/compass-query-bar/src/index.tsx +++ b/packages/compass-query-bar/src/index.tsx @@ -77,4 +77,4 @@ export { queryBarServiceLocator, }; export type { QueryBarService }; -export { type BaseQuery as Query } from './constants/query-properties'; +export type { BaseQuery as Query } from './constants/query-properties'; From 0fd09d360698449d49ca805c9921f5327e358ba1 Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Wed, 3 Jul 2024 14:19:21 -0400 Subject: [PATCH 08/13] added dependencies --- package-lock.json | 2 ++ packages/compass-editor/package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/package-lock.json b/package-lock.json index 7688d157d76..f88448a0084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45120,6 +45120,7 @@ "@lezer/highlight": "^1.1.3", "@mongodb-js/compass-components": "^1.27.0", "@mongodb-js/mongodb-constants": "^0.10.0", + "mongodb-query-parser": "^4.1.2", "polished": "^4.2.2", "prettier": "^2.7.1", "react": "^17.0.2" @@ -56287,6 +56288,7 @@ "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", + "mongodb-query-parser": "^4.1.2", "nyc": "^15.1.0", "polished": "^4.2.2", "prettier": "^2.7.1", diff --git a/packages/compass-editor/package.json b/packages/compass-editor/package.json index 6b29a2525cf..19d4bc56685 100644 --- a/packages/compass-editor/package.json +++ b/packages/compass-editor/package.json @@ -74,6 +74,7 @@ "@lezer/highlight": "^1.1.3", "@mongodb-js/compass-components": "^1.27.0", "@mongodb-js/mongodb-constants": "^0.10.0", + "mongodb-query-parser": "^4.1.2", "polished": "^4.2.2", "prettier": "^2.7.1", "react": "^17.0.2" From c7c6b1870ce6bb03a752023bfb5f7f186c145486 Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Wed, 3 Jul 2024 16:28:36 -0400 Subject: [PATCH 09/13] fixed typecheck error --- .../compass-query-bar/src/components/option-editor.spec.tsx | 4 ++++ 1 file changed, 4 insertions(+) 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 23784ee8560..2df83a8875e 100644 --- a/packages/compass-query-bar/src/components/option-editor.spec.tsx +++ b/packages/compass-query-bar/src/components/option-editor.spec.tsx @@ -181,10 +181,14 @@ describe('OptionEditor', function () { value="" savedQueries={[ { + _id: '1', + _ns: '1', filter: { a: 1 }, _lastExecuted: new Date(), }, { + _id: '1', + _ns: '1', filter: { a: 2 }, sort: { a: -1 }, _lastExecuted: new Date(), From 591ad9195e04adcb8b9753aa90aa3b824f7a9b38 Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Mon, 8 Jul 2024 14:30:29 -0400 Subject: [PATCH 10/13] addressing pr comments --- .../src/components/option-editor.tsx | 14 ++--------- .../src/components/query-bar.tsx | 6 +---- .../query-history-button-popover.tsx | 23 ++++--------------- .../src/stores/query-bar-reducer.ts | 6 ++--- .../src/stores/query-bar-store.ts | 3 +++ 5 files changed, 14 insertions(+), 38 deletions(-) diff --git a/packages/compass-query-bar/src/components/option-editor.tsx b/packages/compass-query-bar/src/components/option-editor.tsx index 06c295357af..72f9c785362 100644 --- a/packages/compass-query-bar/src/components/option-editor.tsx +++ b/packages/compass-query-bar/src/components/option-editor.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef } from 'react'; +import React, { useMemo, useRef } from 'react'; import type { Signal } from '@mongodb-js/compass-components'; import { css, @@ -20,10 +20,7 @@ import { lenientlyFixQuery } from '../query/leniently-fix-query'; import type { RootState } from '../stores/query-bar-store'; import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; import type { RecentQuery } from '@mongodb-js/my-queries-storage'; -import { - applyFromHistory, - fetchSavedQueries, -} from '../stores/query-bar-reducer'; +import { applyFromHistory } from '../stores/query-bar-reducer'; import { getQueryAttributes } from '../utils'; import type { BaseQuery } from '../constants/query-properties'; @@ -103,7 +100,6 @@ type OptionEditorProps = { disabled?: boolean; savedQueries: RecentQuery[]; onApplyQuery: (query: BaseQuery) => void; - loadSavedQueries: () => void; }; export const OptionEditor: React.FunctionComponent = ({ @@ -122,12 +118,7 @@ export const OptionEditor: React.FunctionComponent = ({ disabled = false, savedQueries, onApplyQuery, - loadSavedQueries, }) => { - useEffect(() => { - loadSavedQueries(); - }, [loadSavedQueries]); - const showInsights = usePreference('showInsights'); const editorContainerRef = useRef(null); const editorRef = useRef(null); @@ -251,7 +242,6 @@ const ConnectedOptionEditor = (state: RootState) => ({ const mapDispatchToProps = { onApplyQuery: applyFromHistory, - loadSavedQueries: fetchSavedQueries, }; export default connect(ConnectedOptionEditor, mapDispatchToProps)(OptionEditor); diff --git a/packages/compass-query-bar/src/components/query-bar.tsx b/packages/compass-query-bar/src/components/query-bar.tsx index dd6a2a528ca..d2383c88092 100644 --- a/packages/compass-query-bar/src/components/query-bar.tsx +++ b/packages/compass-query-bar/src/components/query-bar.tsx @@ -30,10 +30,7 @@ import { resetQuery, explainQuery, } from '../stores/query-bar-reducer'; -import { - toggleQueryOptions, - fetchSavedQueries, -} from '../stores/query-bar-reducer'; +import { toggleQueryOptions } from '../stores/query-bar-reducer'; import { isEqualDefaultQuery, isQueryValid } from '../utils/query'; import type { QueryProperty } from '../constants/query-properties'; import { QueryAI } from './query-ai'; @@ -353,7 +350,6 @@ export default connect( if (applied === false) { return; } - dispatch(fetchSavedQueries()); ownProps.onApply?.(applied); }, onReset: () => { diff --git a/packages/compass-query-bar/src/components/query-history-button-popover.tsx b/packages/compass-query-bar/src/components/query-history-button-popover.tsx index 7fe44f426a7..7a88ea8cd86 100644 --- a/packages/compass-query-bar/src/components/query-history-button-popover.tsx +++ b/packages/compass-query-bar/src/components/query-history-button-popover.tsx @@ -9,7 +9,6 @@ import { } from '@mongodb-js/compass-components'; import QueryHistory from './query-history'; -import { fetchSavedQueries } from '../stores/query-bar-reducer'; import { useTrackOnChange, type TrackFunction, @@ -36,11 +35,7 @@ const queryHistoryPopoverStyles = css({ display: 'flex', }); -const QueryHistoryButtonPopover = ({ - onOpenPopover, -}: { - onOpenPopover: () => void; -}) => { +const QueryHistoryButtonPopover = () => { const [isOpen, setIsOpen] = React.useState(false); useTrackOnChange( @@ -55,15 +50,9 @@ const QueryHistoryButtonPopover = ({ undefined ); - const setOpen = useCallback( - (newValue: boolean) => { - if (newValue) { - onOpenPopover(); - } - setIsOpen(newValue); - }, - [onOpenPopover] - ); + const setOpen = useCallback((newValue: boolean) => { + setIsOpen(newValue); + }, []); const closePopover = useCallback(() => { setIsOpen(false); @@ -103,6 +92,4 @@ const QueryHistoryButtonPopover = ({ ); }; -export default connect(null, { - onOpenPopover: fetchSavedQueries, -})(QueryHistoryButtonPopover); +export default connect(null)(QueryHistoryButtonPopover); 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 fe0737cf76a..0996a5c9036 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -394,7 +394,7 @@ const saveRecentQuery = ( query: Omit ): QueryBarThunkAction> => { return async ( - _dispatch, + dispatch, getState, { recentQueryStorage, logger: { debug } } ) => { @@ -424,7 +424,7 @@ const saveRecentQuery = ( existingQuery._id, updateAttributes ); - _dispatch(fetchSavedQueries()); + dispatch(fetchSavedQueries()); return; } @@ -433,7 +433,7 @@ const saveRecentQuery = ( _ns: namespace, _host: host ?? '', }); - _dispatch(fetchSavedQueries()); + dispatch(fetchSavedQueries()); } catch (e) { debug('Failed to save recent query', e); } 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 a3fd82c2f96..c3494d74358 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.ts @@ -14,6 +14,7 @@ import { queryBarReducer, INITIAL_STATE as INITIAL_QUERY_BAR_STATE, QueryBarActions, + fetchSavedQueries, } from './query-bar-reducer'; import { aiQueryReducer } from './ai-query-reducer'; import { getQueryAttributes } from '../utils'; @@ -164,5 +165,7 @@ export function activatePlugin( }); }); + store.dispatch(fetchSavedQueries); + return { store, deactivate: cleanup, context: QueryBarStoreContext }; } From 5d9253333ff69023630f081230b650399a44ee3f Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Tue, 9 Jul 2024 11:14:27 -0400 Subject: [PATCH 11/13] removed fetchSavedQueries from test case --- .../src/components/option-editor.spec.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) 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 2df83a8875e..57035c0b4d1 100644 --- a/packages/compass-query-bar/src/components/option-editor.spec.tsx +++ b/packages/compass-query-bar/src/components/option-editor.spec.tsx @@ -4,10 +4,7 @@ import { cleanup, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { OptionEditor } from './option-editor'; import type { SinonSpy } from 'sinon'; -import { - applyFromHistory, - fetchSavedQueries, -} from '../stores/query-bar-reducer'; +import { applyFromHistory } from '../stores/query-bar-reducer'; import sinon from 'sinon'; class MockPasteEvent extends window.Event { @@ -43,7 +40,6 @@ describe('OptionEditor', function () { value="" savedQueries={[]} onApplyQuery={applyFromHistory} - loadSavedQueries={fetchSavedQueries} > ); @@ -65,7 +61,6 @@ describe('OptionEditor', function () { value="{ foo: 1 }" savedQueries={[]} onApplyQuery={applyFromHistory} - loadSavedQueries={fetchSavedQueries} > ); @@ -87,7 +82,6 @@ describe('OptionEditor', function () { value="" savedQueries={[]} onApplyQuery={applyFromHistory} - loadSavedQueries={fetchSavedQueries} > ); @@ -115,7 +109,6 @@ describe('OptionEditor', function () { value="" savedQueries={[]} onApplyQuery={applyFromHistory} - loadSavedQueries={fetchSavedQueries} > ); @@ -145,7 +138,6 @@ describe('OptionEditor', function () { value="" savedQueries={[]} onApplyQuery={applyFromHistory} - loadSavedQueries={fetchSavedQueries} > ); @@ -164,7 +156,6 @@ describe('OptionEditor', function () { describe('createQueryWithHistoryAutocompleter', function () { let onApplySpy: SinonSpy; - let loadSavedQueriesSpy: SinonSpy; afterEach(function () { cleanup(); @@ -172,7 +163,6 @@ describe('OptionEditor', function () { it('filter applied correctly when autocomplete option is clicked', async function () { onApplySpy = sinon.spy(); - loadSavedQueriesSpy = sinon.spy(); render( ); @@ -213,7 +202,6 @@ describe('OptionEditor', function () { filter: { a: 2 }, sort: { a: -1 }, }); - expect(loadSavedQueriesSpy).to.be.called; }); }); }); From df9cebf2e5020c06bdf465b48ac5b7187cdc211d Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Tue, 9 Jul 2024 14:07:15 -0400 Subject: [PATCH 12/13] fixed dispatch bug, added test case, removed bulk operation from options --- .../src/components/option-editor.tsx | 10 ++- .../query-history-button-popover.tsx | 23 ++++-- .../src/stores/query-bar-store.spec.ts | 74 +++++++++++++++++++ .../src/stores/query-bar-store.ts | 2 +- 4 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 packages/compass-query-bar/src/stores/query-bar-store.spec.ts diff --git a/packages/compass-query-bar/src/components/option-editor.tsx b/packages/compass-query-bar/src/components/option-editor.tsx index 72f9c785362..b362ada98c5 100644 --- a/packages/compass-query-bar/src/components/option-editor.tsx +++ b/packages/compass-query-bar/src/components/option-editor.tsx @@ -149,10 +149,12 @@ export const OptionEditor: React.FunctionComponent = ({ const completer = useMemo(() => { return createQueryWithHistoryAutocompleter( - savedQueries.map((query) => ({ - lastExecuted: query._lastExecuted, - queryProperties: getQueryAttributes(query), - })), + savedQueries + .filter((query) => !('update' in query)) + .map((query) => ({ + lastExecuted: query._lastExecuted, + queryProperties: getQueryAttributes(query), + })), { fields: schemaFields, serverVersion, diff --git a/packages/compass-query-bar/src/components/query-history-button-popover.tsx b/packages/compass-query-bar/src/components/query-history-button-popover.tsx index 7a88ea8cd86..5cd780239eb 100644 --- a/packages/compass-query-bar/src/components/query-history-button-popover.tsx +++ b/packages/compass-query-bar/src/components/query-history-button-popover.tsx @@ -13,6 +13,7 @@ import { useTrackOnChange, type TrackFunction, } from '@mongodb-js/compass-telemetry/provider'; +import { fetchSavedQueries } from '../stores/query-bar-reducer'; const openQueryHistoryButtonStyles = css( { @@ -35,7 +36,11 @@ const queryHistoryPopoverStyles = css({ display: 'flex', }); -const QueryHistoryButtonPopover = () => { +const QueryHistoryButtonPopover = ({ + onOpenPopover, +}: { + onOpenPopover: () => void; +}) => { const [isOpen, setIsOpen] = React.useState(false); useTrackOnChange( @@ -50,9 +55,15 @@ const QueryHistoryButtonPopover = () => { undefined ); - const setOpen = useCallback((newValue: boolean) => { - setIsOpen(newValue); - }, []); + const setOpen = useCallback( + (newValue: boolean) => { + if (newValue) { + onOpenPopover(); + } + setIsOpen(newValue); + }, + [onOpenPopover] + ); const closePopover = useCallback(() => { setIsOpen(false); @@ -92,4 +103,6 @@ const QueryHistoryButtonPopover = () => { ); }; -export default connect(null)(QueryHistoryButtonPopover); +export default connect(null, { + onOpenPopover: fetchSavedQueries, +})(QueryHistoryButtonPopover); diff --git a/packages/compass-query-bar/src/stores/query-bar-store.spec.ts b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts new file mode 100644 index 00000000000..6facce3ab7e --- /dev/null +++ b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts @@ -0,0 +1,74 @@ +import sinon from 'sinon'; +import { activatePlugin } from './query-bar-store'; +import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; +import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; +import { AppRegistry } from 'hadron-app-registry'; +import type { PreferencesAccess } from 'compass-preferences-model'; +import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; +import { expect } from 'chai'; +import { waitFor } from '@testing-library/react'; +describe('createQueryWithHistoryAutocompleter', function () { + let preferences: PreferencesAccess; + let loadAllStub: sinon.SinonStub; + beforeEach(async function () { + loadAllStub = sinon.stub(); + preferences = await createSandboxFromDefaultPreferences(); + }); + afterEach(function () { + sinon.restore(); + }); + it('calls fetchSavedQueries when activatePlugin is called', async function () { + const mockService = { + getQueryFromUserInput: sinon + .stub() + .resolves({ content: { query: { filter: '{_id: 1}' } } }), + }; + const mockDataService = { + sample: sinon.stub().resolves([{ _id: 42 }]), + getConnectionString: sinon.stub().returns({ + hosts: ['localhost:27017'], + }), + }; + activatePlugin( + { + namespace: 'test.coll', + isReadonly: true, + serverVersion: '6.0.0', + isSearchIndexesSupported: true, + isTimeSeries: false, + isClustered: false, + isFLE: false, + isDataLake: false, + isAtlas: false, + }, + { + localAppRegistry: new AppRegistry(), + globalAppRegistry: new AppRegistry(), + dataService: mockDataService, + recentQueryStorageAccess: { + getStorage: () => ({ + loadAll: loadAllStub, + }), + }, + favoriteQueryStorageAccess: { + getStorage: () => ({ + loadAll: loadAllStub, + }), + }, + atlasAuthService: { on: sinon.stub() }, + atlasAiService: mockService, + preferences, + logger: createNoopLogger(), + track: createNoopTrack(), + instance: { isWritable: true, on: sinon.stub() }, + } as any, + { + on: () => {}, + cleanup: () => {}, + } + ); + await waitFor(() => { + expect(loadAllStub).to.have.been.calledTwice; + }); + }); +}); 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 c3494d74358..b6b1d6fecff 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.ts @@ -165,7 +165,7 @@ export function activatePlugin( }); }); - store.dispatch(fetchSavedQueries); + store.dispatch(fetchSavedQueries()); return { store, deactivate: cleanup, context: QueryBarStoreContext }; } From ba4884ead747dae1eb5d13ee6d02c1e85bd7ef15 Mon Sep 17 00:00:00 2001 From: Vivian Xiao Date: Tue, 9 Jul 2024 14:33:12 -0400 Subject: [PATCH 13/13] fix evergreen check --- packages/compass-query-bar/src/stores/query-bar-store.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-query-bar/src/stores/query-bar-store.spec.ts b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts index 6facce3ab7e..370507d03ec 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.spec.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts @@ -65,7 +65,7 @@ describe('createQueryWithHistoryAutocompleter', function () { { on: () => {}, cleanup: () => {}, - } + } as any ); await waitFor(() => { expect(loadAllStub).to.have.been.calledTwice;