Skip to content

Commit 10503e6

Browse files
authored
feat(compass-query-history): Add interactive popover, open query history as popover in new query-bar COMPASS-5678 (#3321)
1 parent 7cb98e3 commit 10503e6

File tree

16 files changed

+569
-99
lines changed

16 files changed

+569
-99
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-collection/src/stores/context.tsx

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ type ContextProps = {
7979
connectionString?: string;
8080
};
8181

82+
type ContextWithAppRegistry = ContextProps & {
83+
globalAppRegistry: AppRegistry;
84+
localAppRegistry: AppRegistry;
85+
};
86+
8287
/**
8388
* Setup a scoped store to the collection.
8489
*
@@ -115,7 +120,7 @@ const setupStore = ({
115120
query,
116121
aggregation,
117122
connectionString,
118-
}: ContextProps) => {
123+
}: ContextWithAppRegistry) => {
119124
const store = role.configureStore({
120125
localAppRegistry,
121126
globalAppRegistry,
@@ -138,7 +143,7 @@ const setupStore = ({
138143
aggregation,
139144
connectionString,
140145
});
141-
localAppRegistry?.registerStore(role.storeName, store);
146+
localAppRegistry.registerStore(role.storeName, store);
142147

143148
return store;
144149
};
@@ -176,7 +181,7 @@ const setupPlugin = ({
176181
sourceName,
177182
connectionString,
178183
key,
179-
}: ContextProps) => {
184+
}: ContextWithAppRegistry) => {
180185
const actions = role.configureActions();
181186
const store = setupStore({
182187
role,
@@ -232,7 +237,7 @@ const setupScopedModals = ({
232237
isFLE,
233238
sourceName,
234239
connectionString,
235-
}: ContextProps) => {
240+
}: ContextWithAppRegistry) => {
236241
const roles = globalAppRegistry?.getRole('Collection.ScopedModal');
237242
if (roles) {
238243
return roles.map((role: any, i: number) => {
@@ -257,6 +262,71 @@ const setupScopedModals = ({
257262
return [];
258263
};
259264

265+
/**
266+
* Setup the query bar plugins. Need to instantiate the store and actions
267+
* and put them in the app registry for use by all the plugins. This way
268+
* there is only 1 query bar store per collection tab instead of one per
269+
* plugin that uses it.
270+
*/
271+
const setupQueryPlugins = ({
272+
globalAppRegistry,
273+
localAppRegistry,
274+
serverVersion,
275+
state,
276+
namespace,
277+
isReadonly,
278+
isTimeSeries,
279+
isClustered,
280+
isFLE,
281+
query,
282+
aggregation,
283+
}: ContextWithAppRegistry): void => {
284+
const queryBarRole = globalAppRegistry.getRole('Query.QueryBar')?.[0];
285+
if (queryBarRole) {
286+
localAppRegistry.registerRole('Query.QueryBar', queryBarRole);
287+
const queryBarActions = setupActions(queryBarRole, localAppRegistry);
288+
setupStore({
289+
role: queryBarRole,
290+
globalAppRegistry,
291+
localAppRegistry,
292+
dataService: state.dataService,
293+
namespace,
294+
serverVersion,
295+
isReadonly,
296+
isTimeSeries,
297+
isClustered,
298+
isFLE,
299+
actions: queryBarActions,
300+
query,
301+
aggregation,
302+
});
303+
}
304+
305+
const queryHistoryRole = globalAppRegistry.getRole('Query.QueryHistory')?.[0];
306+
if (process?.env?.COMPASS_SHOW_NEW_TOOLBARS === 'true' && queryHistoryRole) {
307+
localAppRegistry.registerRole('Query.QueryHistory', queryHistoryRole);
308+
const queryHistoryActions = setupActions(
309+
queryHistoryRole,
310+
localAppRegistry
311+
);
312+
setupStore({
313+
role: queryHistoryRole,
314+
globalAppRegistry,
315+
localAppRegistry,
316+
dataService: state.dataService,
317+
namespace,
318+
serverVersion,
319+
isReadonly,
320+
isTimeSeries,
321+
isClustered,
322+
isFLE,
323+
actions: queryHistoryActions,
324+
query,
325+
aggregation,
326+
});
327+
}
328+
};
329+
260330
/**
261331
* Create the context in which a tab is created.
262332
*
@@ -306,25 +376,16 @@ const createContext = ({
306376
const views: JSX.Element[] = [];
307377
const queryHistoryIndexes: number[] = [];
308378

309-
// Setup the query bar plugin. Need to instantiate the store and actions
310-
// and put them in the app registry for use by all the plugins. This way
311-
// there is only 1 query bar store per collection tab instead of one per
312-
// plugin that uses it.
313-
const queryBarRole = globalAppRegistry.getRole('Query.QueryBar')[0];
314-
localAppRegistry.registerRole('Query.QueryBar', queryBarRole);
315-
const queryBarActions = setupActions(queryBarRole, localAppRegistry);
316-
setupStore({
317-
role: queryBarRole,
379+
setupQueryPlugins({
318380
globalAppRegistry,
319381
localAppRegistry,
320-
dataService: state.dataService,
321-
namespace,
322382
serverVersion,
383+
state,
384+
namespace,
323385
isReadonly,
324386
isTimeSeries,
325387
isClustered,
326388
isFLE,
327-
actions: queryBarActions,
328389
query,
329390
aggregation,
330391
});

packages/compass-components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"@react-stately/tooltip": "^3.0.5",
7474
"@types/lodash": "^4.14.172",
7575
"bson": "^4.6.1",
76+
"focus-trap-react": "^8.4.2",
7677
"hadron-document": "^8.0.0",
7778
"hadron-type-checker": "^7.0.0",
7879
"lodash": "^4.17.21",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React from 'react';
2+
import { render, screen, cleanup } from '@testing-library/react';
3+
import { expect } from 'chai';
4+
import sinon from 'sinon';
5+
6+
import { InteractivePopover } from './interactive-popover';
7+
8+
const innerContentTestId = 'testing-inner-content';
9+
10+
function renderPopover(
11+
props?: Partial<React.ComponentProps<typeof InteractivePopover>>
12+
) {
13+
const openSpy = sinon.spy();
14+
15+
const popoverContent = ({ onClose }) => (
16+
<>
17+
<button onClick={() => {}} data-testid={innerContentTestId}>
18+
Action Button
19+
</button>
20+
<div>inner content</div>
21+
<button onClick={onClose}>Close Button</button>
22+
</>
23+
);
24+
25+
return render(
26+
<InteractivePopover
27+
className=""
28+
open={false}
29+
setOpen={openSpy}
30+
trigger={({ onClick, ref, children }) => (
31+
<>
32+
<button onClick={onClick} ref={ref}>
33+
Trigger Button Text
34+
</button>
35+
{children}
36+
</>
37+
)}
38+
{...props}
39+
>
40+
{popoverContent}
41+
</InteractivePopover>
42+
);
43+
}
44+
45+
describe('InteractivePopover Component', function () {
46+
afterEach(function () {
47+
cleanup();
48+
});
49+
50+
it('when open it should show the popover content', function () {
51+
renderPopover({
52+
open: true,
53+
});
54+
expect(screen.getByTestId(innerContentTestId)).to.be.visible;
55+
});
56+
57+
it('when closed it should not show the popover content', function () {
58+
renderPopover({
59+
open: false,
60+
});
61+
expect(screen.queryByTestId(innerContentTestId)).to.not.exist;
62+
});
63+
64+
it('should render the trigger', function () {
65+
renderPopover({
66+
open: false,
67+
});
68+
const button = screen.getByRole('button');
69+
expect(button).to.be.visible;
70+
expect(screen.getByText('Trigger Button Text')).to.be.visible;
71+
});
72+
73+
it('when closed and the trigger is clicked it should call to open', function () {
74+
const openSpy = sinon.fake();
75+
76+
renderPopover({
77+
open: false,
78+
setOpen: openSpy,
79+
});
80+
expect(openSpy.calledOnce).to.be.false;
81+
82+
const button = screen.getByText('Trigger Button Text');
83+
button.click();
84+
expect(openSpy.calledOnce).to.be.true;
85+
expect(openSpy.firstCall.firstArg).to.equal(true);
86+
});
87+
88+
it('when open and the trigger is clicked it should call to close', function () {
89+
const openSpy = sinon.fake();
90+
91+
renderPopover({
92+
open: true,
93+
setOpen: openSpy,
94+
});
95+
expect(openSpy.calledOnce).to.be.false;
96+
97+
const button = screen.getByText('Trigger Button Text');
98+
button.click();
99+
expect(openSpy.calledOnce).to.be.true;
100+
expect(openSpy.firstCall.firstArg).to.equal(false);
101+
});
102+
});

0 commit comments

Comments
 (0)