From fc33eba7338ffc5d0352b6f58d29e2a71beaf84a Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Thu, 9 Nov 2023 11:34:59 +0100 Subject: [PATCH 01/42] chore: change to Modal instead of FormModal so the toolbar can be changed --- .../src/components/bulk-update-dialog.tsx | 195 +++++++++++------- 1 file changed, 116 insertions(+), 79 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 74637fa7f8b..4624871c2de 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -3,7 +3,6 @@ import type { UpdatePreview } from 'mongodb-data-service'; import HadronDocument from 'hadron-document'; import { toJSString } from 'mongodb-query-parser'; import { - FormModal, css, cx, spacing, @@ -16,6 +15,11 @@ import { Link, useDarkMode, usePrevious, + Modal, + ModalFooter, + Button, + ModalHeader, + ModalBody, } from '@mongodb-js/compass-components'; import type { Annotation } from '@mongodb-js/compass-editor'; import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor'; @@ -107,6 +111,22 @@ const updatePreviewStyles = css({ gap: spacing[3], }); +const modalFooterToolbarSpacingStyles = css({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', +}); + +const modalFooterFormActionsStyles = css({ + display: 'flex', + flexDirection: 'row', + gap: spacing[2], +}); + +const modalFooterAdditionalActionsStyles = css({ + alignSelf: 'start', +}); + export type BulkUpdateDialogProps = { isOpen: boolean; ns: string; @@ -178,93 +198,110 @@ export default function BulkUpdateDialog({ }, [isOpen, wasOpen, updateText]); return ( - -
-
-
- -
- -
- - - - Learn more about Update syntax - - - - ({})} - className={codeEditorStyles} - annotations={annotations} + + +
+
+
+ +
-
- {syntaxError && ( - - {syntaxError.message} - - )} - {serverError && !syntaxError && ( - - {serverError.message} - +
+ + + + Learn more about Update syntax + + + - -
-
-
- -
- {previewDocuments.map((doc: HadronDocument, index: number) => { - return ( - + ({})} + className={codeEditorStyles} + annotations={annotations} /> - ); - })} + +
+ {syntaxError && ( + + {syntaxError.message} + + )} + {serverError && !syntaxError && ( + + {serverError.message} + + )} +
+ +
+
+
+ +
+ {previewDocuments.map((doc: HadronDocument, index: number) => { + return ( + + ); + })} +
-
- +
+ +
+ +
+
+ + +
+
+ ); } From 125dafc682bf60a14740ea438f0fa9397ffdda37 Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Thu, 9 Nov 2023 11:35:40 +0100 Subject: [PATCH 02/42] chore: remove not necessary styles --- packages/compass-crud/src/components/bulk-update-dialog.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 4624871c2de..06dc8984748 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -123,9 +123,7 @@ const modalFooterFormActionsStyles = css({ gap: spacing[2], }); -const modalFooterAdditionalActionsStyles = css({ - alignSelf: 'start', -}); +const modalFooterAdditionalActionsStyles = css({}); export type BulkUpdateDialogProps = { isOpen: boolean; From 01d27065fa4f499b041dbf9542141ada75adccca Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Thu, 9 Nov 2023 12:31:45 +0100 Subject: [PATCH 03/42] chore: add favorite icon to save button --- .../src/components/bulk-update-dialog.tsx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 06dc8984748..76cbc0bfd71 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -20,6 +20,7 @@ import { Button, ModalHeader, ModalBody, + Icon, } from '@mongodb-js/compass-components'; import type { Annotation } from '@mongodb-js/compass-editor'; import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor'; @@ -168,9 +169,6 @@ export default function BulkUpdateDialog({ updateBulkUpdatePreview(value); }; - const title = - count === undefined ? 'Update documents' : `Update documents (${count})`; - const annotations = useMemo(() => { if (syntaxError?.loc?.index) { return [ @@ -196,13 +194,8 @@ export default function BulkUpdateDialog({ }, [isOpen, wasOpen, updateText]); return ( - - + +
@@ -283,7 +276,8 @@ export default function BulkUpdateDialog({
From ca32331f24e854df05d74c805993f90553edd687 Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Thu, 9 Nov 2023 12:32:12 +0100 Subject: [PATCH 04/42] chore: add update query to saved queries --- packages/my-queries-storage/src/query-storage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/my-queries-storage/src/query-storage.ts b/packages/my-queries-storage/src/query-storage.ts index b9eeaa57ff3..d7234d200d1 100644 --- a/packages/my-queries-storage/src/query-storage.ts +++ b/packages/my-queries-storage/src/query-storage.ts @@ -8,6 +8,7 @@ const queryProps = { sort: z.any().optional(), skip: z.number().optional(), limit: z.number().optional(), + update: z.any().optional(), }; const commonMetadata = { From d1c3b208ee24f48145bc7355fe14719a68cabc39 Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Thu, 9 Nov 2023 13:33:20 +0100 Subject: [PATCH 05/42] chore: implement interactivepopover for the bulk update dialog --- .../src/components/interactive-popover.tsx | 30 ++--- .../src/components/bulk-update-dialog.tsx | 103 +++++++++++++++++- 2 files changed, 115 insertions(+), 18 deletions(-) diff --git a/packages/compass-components/src/components/interactive-popover.tsx b/packages/compass-components/src/components/interactive-popover.tsx index 332b9c8a791..7f935535a7f 100644 --- a/packages/compass-components/src/components/interactive-popover.tsx +++ b/packages/compass-components/src/components/interactive-popover.tsx @@ -50,6 +50,7 @@ type InteractivePopoverProps = { ref: React.LegacyRef; children: React.ReactNode; }) => React.ReactElement; + hasCustomCloseButton: boolean; open: boolean; setOpen: (open: boolean) => void; /** @@ -67,6 +68,7 @@ function InteractivePopover({ className, children, trigger, + hasCustomCloseButton, open, setOpen, containedElements = [], @@ -182,19 +184,21 @@ function InteractivePopover({ > {children} - { - evt.stopPropagation(); - onClose(); - }} - aria-label="Close" - id={closeButtonId} - ref={closeButtonRef} - > - - + {!hasCustomCloseButton && ( + { + evt.stopPropagation(); + onClose(); + }} + aria-label="Close" + id={closeButtonId} + ref={closeButtonRef} + > + + + )}
diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 76cbc0bfd71..10c9b469be4 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState, useEffect } from 'react'; +import React, { useMemo, useState, useEffect, useCallback } from 'react'; import type { UpdatePreview } from 'mongodb-data-service'; import HadronDocument from 'hadron-document'; import { toJSString } from 'mongodb-query-parser'; @@ -21,6 +21,8 @@ import { ModalHeader, ModalBody, Icon, + InteractivePopover, + TextInput, } from '@mongodb-js/compass-components'; import type { Annotation } from '@mongodb-js/compass-editor'; import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor'; @@ -126,6 +128,96 @@ const modalFooterFormActionsStyles = css({ const modalFooterAdditionalActionsStyles = css({}); +const inlineSaveQueryModalStyles = css({ + display: 'flex', + flexDirection: 'row', + padding: spacing[3], + gap: spacing[3], +}); + +type InlineSaveQueryModalProps = { + onSave: (name: string) => void; +}; + +const InlineSaveQueryModal: React.FunctionComponent< + InlineSaveQueryModalProps +> = ({ onSave }) => { + const [open, setOpen] = useState(false); + const [favoriteName, setFavoriteName] = useState(''); + const [valid, setValid] = useState(false); + + const cleanClose = useCallback(() => { + setOpen(false); + setFavoriteName(''); + }, [setOpen, setFavoriteName]); + + const onClickSave = useCallback(() => { + onSave(favoriteName); + cleanClose(); + }, [onSave, favoriteName, cleanClose]); + + const updateFavoriteName = useCallback( + (ev: React.ChangeEvent) => { + const target = ev.target as any as { value: string }; + const favoriteName: string = (target.value || '').trim(); + + setFavoriteName(favoriteName); + setValid(favoriteName !== ''); + }, + [setFavoriteName] + ); + + const handleSpecialKeyboardEvents = useCallback( + (ev: React.KeyboardEvent) => { + if (ev.key === 'Enter') { + onSave(favoriteName); + cleanClose(); + } else if (ev.key === 'Escape') { + cleanClose(); + } + }, + [favoriteName, onSave, cleanClose] + ); + + return ( + { + return ( + + ); + }} + hasCustomCloseButton={true} + open={open} + setOpen={setOpen} + > +
+ + + +
+
+ ); +}; + export type BulkUpdateDialogProps = { isOpen: boolean; ns: string; @@ -275,10 +367,11 @@ export default function BulkUpdateDialog({
- + { + return; + }} + />
); }} + align="top" hasCustomCloseButton={true} open={open} setOpen={setOpen} > -
+
Date: Thu, 9 Nov 2023 17:14:14 +0100 Subject: [PATCH 07/42] chore: store the query in the favorite storage --- .../src/components/bulk-update-dialog.tsx | 8 ++--- .../src/components/document-list.tsx | 7 ++++ .../compass-crud/src/stores/crud-store.ts | 33 +++++++++++++++++++ .../my-queries-storage/src/query-storage.ts | 13 ++++++++ 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 727d064e78d..1b10500928a 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -243,6 +243,7 @@ export type BulkUpdateDialogProps = { closeBulkUpdateDialog: () => void; updateBulkUpdatePreview: (updateText: string) => void; runBulkUpdate: () => void; + saveUpdateQuery: (name: string) => void; }; export default function BulkUpdateDialog({ @@ -257,6 +258,7 @@ export default function BulkUpdateDialog({ closeBulkUpdateDialog, updateBulkUpdatePreview, runBulkUpdate, + saveUpdateQuery, }: BulkUpdateDialogProps) { const darkMode = useDarkMode(); @@ -380,11 +382,7 @@ export default function BulkUpdateDialog({
- { - return; - }} - /> +
+ )}
From 706760d8b17fc48cbb7896315d64b153dd3df240 Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Mon, 13 Nov 2023 12:18:57 +0100 Subject: [PATCH 19/42] chore: fix interactive popover interactions and add test to bulk update dialog --- .../components/interactive-popover.spec.tsx | 2 +- .../src/components/interactive-popover.tsx | 29 ++++++++++---- .../components/bulk-update-dialog.spec.tsx | 38 ++++++++++++++++--- .../src/components/bulk-update-dialog.tsx | 14 ++++--- .../src/stores/crud-store.spec.ts | 4 +- 5 files changed, 65 insertions(+), 22 deletions(-) diff --git a/packages/compass-components/src/components/interactive-popover.spec.tsx b/packages/compass-components/src/components/interactive-popover.spec.tsx index 760af5dad43..5ddab8a04bf 100644 --- a/packages/compass-components/src/components/interactive-popover.spec.tsx +++ b/packages/compass-components/src/components/interactive-popover.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, cleanup, prettyDOM } from '@testing-library/react'; +import { render, screen, cleanup } from '@testing-library/react'; import { expect } from 'chai'; import sinon from 'sinon'; diff --git a/packages/compass-components/src/components/interactive-popover.tsx b/packages/compass-components/src/components/interactive-popover.tsx index 669545a3c03..e4656b62b5d 100644 --- a/packages/compass-components/src/components/interactive-popover.tsx +++ b/packages/compass-components/src/components/interactive-popover.tsx @@ -93,14 +93,27 @@ function InteractivePopover({ }); }, [setOpen]); - const onClickTrigger = useCallback(() => { - if (open) { - onClose(); - return; - } + const onClickTrigger = useCallback( + (event: MouseEvent | TouchEvent) => { + if (open) { + if ( + containedElements.some((selector) => { + return document + .querySelector(selector) + ?.contains(event.target as Node); + }) + ) { + return; + } + + onClose(); + return; + } - setOpen(!open); - }, [open, setOpen, onClose]); + setOpen(!open); + }, + [open, setOpen, onClose, containedElements] + ); // When the popover is open, close it when an item that isn't the popover // is clicked. @@ -150,7 +163,7 @@ function InteractivePopover({ const closeButtonId = useId('close-button-id'); return trigger({ - onClick: onClickTrigger, + onClick: onClickTrigger as any, ref: triggerRef, children: ( {}} updateBulkUpdatePreview={() => {}} runBulkUpdate={() => {}} + saveUpdateQuery={() => {}} {...props} /> ); @@ -60,8 +67,9 @@ describe('BulkUpdateDialog Component', function () { ).to.have.lengthOf(1); // buttons - expect(screen.getByRole('button', { name: 'Close' })).to.exist; - expect(screen.getByRole('button', { name: 'Update documents' })).to.exist; + expect(screen.getByRole('button', { name: 'Cancel' })).to.exist; + expect(screen.getByRole('button', { name: 'Update 42 documents' })).to + .exist; }); it('resets if the modal is re-opened', async function () { @@ -87,6 +95,7 @@ describe('BulkUpdateDialog Component', function () { closeBulkUpdateDialog={() => {}} updateBulkUpdatePreview={() => {}} runBulkUpdate={() => {}} + saveUpdateQuery={() => {}} /> ); @@ -109,6 +118,7 @@ describe('BulkUpdateDialog Component', function () { closeBulkUpdateDialog={() => {}} updateBulkUpdatePreview={() => {}} runBulkUpdate={() => {}} + saveUpdateQuery={() => {}} /> ); @@ -125,15 +135,31 @@ describe('BulkUpdateDialog Component', function () { const onCloseSpy = sinon.spy(); renderBulkUpdateDialog({ closeBulkUpdateDialog: onCloseSpy }); - userEvent.click(screen.getByRole('button', { name: 'Close' })); + userEvent.click(screen.getByRole('button', { name: 'Cancel' })); expect(onCloseSpy).to.have.been.calledOnce; }); it('runs the update when the update button is clicked', function () { const onUpdateSpy = sinon.spy(); - renderBulkUpdateDialog({ runBulkUpdate: onUpdateSpy }); + renderBulkUpdateDialog({ runBulkUpdate: onUpdateSpy, count: 60 }); - userEvent.click(screen.getByRole('button', { name: 'Update documents' })); + userEvent.click( + screen.getByRole('button', { name: 'Update 60 documents' }) + ); expect(onUpdateSpy).to.have.been.calledOnce; }); + + it('saves the query when a name is provided', function () { + const saveUpdateQuerySpy = sinon.spy(); + renderBulkUpdateDialog({ saveUpdateQuery: saveUpdateQuerySpy }); + + userEvent.click(screen.getByTestId('inline-save-query-modal-opener')); + userEvent.type( + screen.getByTestId('inline-save-query-modal-input'), + 'MySavedQuery' + ); + + userEvent.click(screen.getByTestId('inline-save-query-modal-submit')); + expect(saveUpdateQuerySpy).to.have.been.calledOnceWith('MySavedQuery'); + }); }); diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 1b10500928a..8342d3b8cbe 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -143,10 +143,7 @@ type InlineSaveQueryModalProps = { onSave: (name: string) => void; }; -const inlineSaveQueryModalContainedElements = [ - '#inline-save-query-modal', - '#inline-save-query-modal-input', -]; +const inlineSaveQueryModalContainedElements = ['#inline-save-query-modal *']; const InlineSaveQueryModal: React.FunctionComponent< InlineSaveQueryModalProps @@ -197,6 +194,7 @@ const InlineSaveQueryModal: React.FunctionComponent< - )}
From 8d1b4e2538a1284cb62c69efcd14346b748155b2 Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Tue, 14 Nov 2023 14:23:00 +0100 Subject: [PATCH 34/42] chore: use global app registry for interplugin comms and fix test on update bulk modal --- .../compass-crud/src/components/bulk-update-dialog.tsx | 7 ++++++- packages/compass-crud/src/stores/crud-store.ts | 2 +- .../src/components/query-history-button-popover.tsx | 4 +++- packages/compass-query-bar/src/stores/query-bar-reducer.ts | 5 +++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index 7d2f182fbfa..f02f1639127 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -191,6 +191,7 @@ const InlineSaveQueryModal: React.FunctionComponent< }} align="top" hasCustomCloseButton={true} + customTrapFallback={'#inline-save-query-modal-cancel-button'} open={open} setOpen={setOpen} > @@ -212,7 +213,11 @@ const InlineSaveQueryModal: React.FunctionComponent< > Save -
diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index 3595c1c0eca..07f6d6f8d4b 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -2003,7 +2003,7 @@ const configureStore = (options: CrudStoreOptions & GridStoreOptions) => { void store.refreshDocuments(); }); - instance.on( + globalAppRegistry.on( 'favorites-open-bulk-update-favorite', (query: QueryState & { update: BSONObject }) => { void store.onQueryChanged(query); 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 c603851f1fc..f07fcf463f3 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 @@ -64,7 +64,9 @@ const QueryHistoryButtonPopover = ({ [onOpenPopover] ); - const closePopover = useCallback(() => setIsOpen(false), [setIsOpen]); + const closePopover = useCallback(() => { + setIsOpen(false); + }, [setIsOpen]); return ( , ApplyFromHistoryAction> => { - return async (dispatch, getState, { localAppRegistry }) => { + return async (dispatch, getState, { globalAppRegistry }) => { dispatch({ type: QueryBarActions.ApplyFromHistory, query, } as ApplyFromHistoryAction); if (query.update) { - localAppRegistry?.emit('favorites-open-bulk-update-favorite', query); + globalAppRegistry?.emit('favorites-open-bulk-update-favorite', query); } return Promise.resolve(); From 8be853cbcd2b51400dd2bd68e69f487dc0e893c6 Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Tue, 14 Nov 2023 14:33:20 +0100 Subject: [PATCH 35/42] chore: fix linting complains --- packages/compass-query-bar/src/stores/query-bar-reducer.ts | 1 - 1 file changed, 1 deletion(-) 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 c7e07098f45..c0f9b6724ad 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -30,7 +30,6 @@ import type { RecentQuery, FavoriteQuery, } from '@mongodb-js/my-queries-storage'; -import { globalAppRegistry } from 'hadron-app-registry'; const { debug } = createLoggerAndTelemetry('COMPASS-QUERY-BAR-UI'); type QueryBarState = { From 7a0903facf7f195d1272e9d6c6b9aaf97a5ef00f Mon Sep 17 00:00:00 2001 From: Kevin Mas Ruiz Date: Tue, 14 Nov 2023 15:52:37 +0100 Subject: [PATCH 36/42] chore: remove comment that is not informative enough --- packages/compass-crud/src/components/bulk-update-dialog.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compass-crud/src/components/bulk-update-dialog.tsx b/packages/compass-crud/src/components/bulk-update-dialog.tsx index f02f1639127..286e22bb171 100644 --- a/packages/compass-crud/src/components/bulk-update-dialog.tsx +++ b/packages/compass-crud/src/components/bulk-update-dialog.tsx @@ -173,7 +173,6 @@ const InlineSaveQueryModal: React.FunctionComponent< return ( { return (