Skip to content

Commit a1dfac5

Browse files
authored
chore: add a button to save an update query from the Bulk Update modal COMPASS-7331 (#5105)
* chore: change to Modal instead of FormModal so the toolbar can be changed * chore: remove not necessary styles * chore: add favorite icon to save button * chore: add update query to saved queries * chore: implement interactivepopover for the bulk update dialog * chore: make new hasCustomCloseButton prop optional * chore: store the query in the favorite storage * chore: fix card styles * chore: show all required fields in favorites * chore: open update modal from favorites * chore: void promise * chore: fix linting issues * chore: add opens in new tab marker to favorite items * chore: close popover when an update favorite is chosen * chore: force toasts to be on top of overlays * chore: show opens in modal marker always * chore: add test for interactive popover * chore: fix test related to close button * chore: fix interactive popover interactions and add test to bulk update dialog * chore: test save query interaction * chore: remove .only * chore: test favorite storage * chore: test updatemany card * chore: test query bar changes * chore: fix linter * chore: fix some more linting issues * chore: add dependency to queries storage * chore: linting * chore: fix some more linting issues * chore: fix issues * chore: minor refactors * chore: fix linting issues * chore: allow a custom fallback * chore: use global app registry for interplugin comms and fix test on update bulk modal * chore: fix linting complains * chore: remove comment that is not informative enough * chore: remove cast to any * chore: rename trap fallback and use localAppRegistry * chore: group localAppRegistry events together * chore: rename close button override prop * chore: linting issues * chore: fix linting issues
1 parent f672f6e commit a1dfac5

25 files changed

+582
-150
lines changed

packages/compass-components/src/components/interactive-popover.spec.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ function renderPopover(
1414
<InteractivePopover
1515
className=""
1616
open={false}
17+
hideCloseButton={props?.hideCloseButton}
18+
customFocusTrapFallback={`#${innerContentTestId}`}
1719
setOpen={() => {}}
1820
trigger={({ onClick, ref, children }) => (
1921
<>
@@ -29,6 +31,7 @@ function renderPopover(
2931
<button
3032
type="button"
3133
onClick={() => {}}
34+
id={innerContentTestId}
3235
data-testid={innerContentTestId}
3336
>
3437
Action Button
@@ -111,4 +114,14 @@ describe('InteractivePopover Component', function () {
111114
expect(openSpy.calledOnce).to.be.true;
112115
expect(openSpy.firstCall.firstArg).to.equal(false);
113116
});
117+
118+
it('when open with a custom button, the general close button is not visible', function () {
119+
renderPopover({
120+
open: true,
121+
hideCloseButton: true,
122+
});
123+
124+
expect(screen.queryByTestId('interactive-popover-close-button')).to.not
125+
.exist;
126+
});
114127
});

packages/compass-components/src/components/interactive-popover.tsx

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type InteractivePopoverProps = {
5050
ref: React.LegacyRef<HTMLButtonElement>;
5151
children: React.ReactNode;
5252
}) => React.ReactElement;
53+
hideCloseButton?: boolean;
54+
customFocusTrapFallback?: string;
5355
open: boolean;
5456
setOpen: (open: boolean) => void;
5557
/**
@@ -67,6 +69,8 @@ function InteractivePopover({
6769
className,
6870
children,
6971
trigger,
72+
hideCloseButton = false,
73+
customFocusTrapFallback = undefined,
7074
open,
7175
setOpen,
7276
containedElements = [],
@@ -91,14 +95,27 @@ function InteractivePopover({
9195
});
9296
}, [setOpen]);
9397

94-
const onClickTrigger = useCallback(() => {
95-
if (open) {
96-
onClose();
97-
return;
98-
}
98+
const onClickTrigger = useCallback(
99+
(event) => {
100+
if (open) {
101+
if (
102+
containedElements.some((selector) => {
103+
return document
104+
.querySelector(selector)
105+
?.contains(event.target as Node);
106+
})
107+
) {
108+
return;
109+
}
110+
111+
onClose();
112+
return;
113+
}
99114

100-
setOpen(!open);
101-
}, [open, setOpen, onClose]);
115+
setOpen(!open);
116+
},
117+
[open, setOpen, onClose, containedElements]
118+
);
102119

103120
// When the popover is open, close it when an item that isn't the popover
104121
// is clicked.
@@ -167,7 +184,7 @@ function InteractivePopover({
167184
focusTrapOptions={{
168185
clickOutsideDeactivates: true,
169186
// Tests fail without a fallback. (https://github.com/focus-trap/focus-trap-react/issues/91)
170-
fallbackFocus: `#${closeButtonId}`,
187+
fallbackFocus: customFocusTrapFallback || `#${closeButtonId}`,
171188
}}
172189
>
173190
<div
@@ -182,19 +199,21 @@ function InteractivePopover({
182199
>
183200
{children}
184201

185-
<IconButton
186-
className={cx(closeButtonStyles, closeButtonClassName)}
187-
data-testid="interactive-popover-close-button"
188-
onClick={(evt) => {
189-
evt.stopPropagation();
190-
onClose();
191-
}}
192-
aria-label="Close"
193-
id={closeButtonId}
194-
ref={closeButtonRef}
195-
>
196-
<Icon glyph="X" />
197-
</IconButton>
202+
{!hideCloseButton && (
203+
<IconButton
204+
className={cx(closeButtonStyles, closeButtonClassName)}
205+
data-testid="interactive-popover-close-button"
206+
onClick={(evt) => {
207+
evt.stopPropagation();
208+
onClose();
209+
}}
210+
aria-label="Close"
211+
id={closeButtonId}
212+
ref={closeButtonRef}
213+
>
214+
<Icon glyph="X" />
215+
</IconButton>
216+
)}
198217
</div>
199218
</FocusTrap>
200219
</Popover>

packages/compass-components/src/hooks/use-toast.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ToastProvider,
1111
useToast as useLeafygreenToast,
1212
} from '../components/leafygreen';
13+
import { css } from '@leafygreen-ui/emotion';
1314

1415
export type ToastProperties = Pick<
1516
ToastProps,
@@ -153,6 +154,7 @@ const _ToastArea: React.FunctionComponent = ({ children }) => {
153154
);
154155
};
155156

157+
const toastAreaFronLayerStyles = css({ zIndex: 1 });
156158
const ToastAreaMountedContext = React.createContext(false);
157159

158160
export const ToastArea: React.FunctionComponent = ({ children }) => {
@@ -162,7 +164,7 @@ export const ToastArea: React.FunctionComponent = ({ children }) => {
162164

163165
return (
164166
<ToastAreaMountedContext.Provider value={true}>
165-
<ToastProvider>
167+
<ToastProvider portalClassName={toastAreaFronLayerStyles}>
166168
<_ToastArea>{children}</_ToastArea>
167169
</ToastProvider>
168170
</ToastAreaMountedContext.Provider>

packages/compass-crud/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@mongodb-js/compass-components": "^1.19.0",
6060
"@mongodb-js/compass-editor": "^0.18.0",
6161
"@mongodb-js/compass-logging": "^1.2.6",
62+
"@mongodb-js/my-queries-storage": "^0.2.1",
6263
"@mongodb-js/explain-plan-helper": "^1.1.4",
6364
"bson": "^6.0.0",
6465
"compass-preferences-model": "^2.15.6",
@@ -108,6 +109,7 @@
108109
"@mongodb-js/compass-editor": "^0.18.0",
109110
"@mongodb-js/compass-logging": "^1.2.6",
110111
"@mongodb-js/explain-plan-helper": "^1.1.4",
112+
"@mongodb-js/my-queries-storage": "^0.2.1",
111113
"bson": "^6.0.0",
112114
"compass-preferences-model": "^2.15.6",
113115
"hadron-document": "^8.4.3",

packages/compass-crud/src/components/bulk-update-dialog.spec.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ function renderBulkUpdateDialog(
2626
closeBulkUpdateDialog={() => {}}
2727
updateBulkUpdatePreview={() => {}}
2828
runBulkUpdate={() => {}}
29+
saveUpdateQuery={() => {}}
2930
{...props}
3031
/>
3132
);
@@ -60,8 +61,9 @@ describe('BulkUpdateDialog Component', function () {
6061
).to.have.lengthOf(1);
6162

6263
// buttons
63-
expect(screen.getByRole('button', { name: 'Close' })).to.exist;
64-
expect(screen.getByRole('button', { name: 'Update documents' })).to.exist;
64+
expect(screen.getByRole('button', { name: 'Cancel' })).to.exist;
65+
expect(screen.getByRole('button', { name: 'Update 42 documents' })).to
66+
.exist;
6567
});
6668

6769
it('resets if the modal is re-opened', async function () {
@@ -87,6 +89,7 @@ describe('BulkUpdateDialog Component', function () {
8789
closeBulkUpdateDialog={() => {}}
8890
updateBulkUpdatePreview={() => {}}
8991
runBulkUpdate={() => {}}
92+
saveUpdateQuery={() => {}}
9093
/>
9194
);
9295

@@ -109,6 +112,7 @@ describe('BulkUpdateDialog Component', function () {
109112
closeBulkUpdateDialog={() => {}}
110113
updateBulkUpdatePreview={() => {}}
111114
runBulkUpdate={() => {}}
115+
saveUpdateQuery={() => {}}
112116
/>
113117
);
114118

@@ -125,15 +129,31 @@ describe('BulkUpdateDialog Component', function () {
125129
const onCloseSpy = sinon.spy();
126130
renderBulkUpdateDialog({ closeBulkUpdateDialog: onCloseSpy });
127131

128-
userEvent.click(screen.getByRole('button', { name: 'Close' }));
132+
userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
129133
expect(onCloseSpy).to.have.been.calledOnce;
130134
});
131135

132136
it('runs the update when the update button is clicked', function () {
133137
const onUpdateSpy = sinon.spy();
134-
renderBulkUpdateDialog({ runBulkUpdate: onUpdateSpy });
138+
renderBulkUpdateDialog({ runBulkUpdate: onUpdateSpy, count: 60 });
135139

136-
userEvent.click(screen.getByRole('button', { name: 'Update documents' }));
140+
userEvent.click(
141+
screen.getByRole('button', { name: 'Update 60 documents' })
142+
);
137143
expect(onUpdateSpy).to.have.been.calledOnce;
138144
});
145+
146+
it('saves the query when a name is provided', function () {
147+
const saveUpdateQuerySpy = sinon.spy();
148+
renderBulkUpdateDialog({ saveUpdateQuery: saveUpdateQuerySpy });
149+
150+
userEvent.click(screen.getByTestId('inline-save-query-modal-opener'));
151+
userEvent.type(
152+
screen.getByTestId('inline-save-query-modal-input'),
153+
'MySavedQuery'
154+
);
155+
156+
userEvent.click(screen.getByTestId('inline-save-query-modal-submit'));
157+
expect(saveUpdateQuerySpy).to.have.been.calledOnceWith('MySavedQuery');
158+
});
139159
});

0 commit comments

Comments
 (0)