Skip to content

Commit d2b0388

Browse files
committed
chore(indexes): add tests; cleanup polling on plugin deactivate
1 parent 3fb2251 commit d2b0388

File tree

9 files changed

+279
-79
lines changed

9 files changed

+279
-79
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import React from 'react';
2+
import Sinon from 'sinon';
3+
import {
4+
CompassIndexesPlugin as CompassIndexesSubtab,
5+
CompassIndexesHadronPlugin,
6+
} from './index';
7+
import {
8+
createDefaultConnectionInfo,
9+
createPluginTestHelpers,
10+
screen,
11+
userEvent,
12+
waitFor,
13+
within,
14+
} from '@mongodb-js/testing-library-compass';
15+
import { expect } from 'chai';
16+
import {
17+
indexCreationStarted,
18+
prepareInProgressIndex,
19+
rollingIndexTimeoutCheck,
20+
} from './modules/regular-indexes';
21+
22+
describe('CompassIndexesPlugin', function () {
23+
const sandbox = Sinon.createSandbox();
24+
25+
const dataService = {
26+
indexes: () => [],
27+
createIndex: () => ({}),
28+
};
29+
30+
const atlasService = {
31+
automationAgentRequest: () => ({}),
32+
automationAgentAwait: () => ({ response: [] }),
33+
authenticatedFetch: () => undefined,
34+
cloudEndpoint: () => undefined,
35+
};
36+
37+
const renderHelpers = createPluginTestHelpers(
38+
CompassIndexesHadronPlugin.withMockServices({
39+
dataService,
40+
atlasService,
41+
instance: {
42+
on: () => undefined,
43+
removeListener: () => undefined,
44+
isWritable: true,
45+
},
46+
collection: {
47+
on: () => undefined,
48+
removeListener: () => undefined,
49+
toJSON: () => ({}),
50+
},
51+
} as any)
52+
);
53+
54+
function render() {
55+
return renderHelpers.renderWithActiveConnection(
56+
<CompassIndexesSubtab.content></CompassIndexesSubtab.content>,
57+
{
58+
...createDefaultConnectionInfo(),
59+
atlasMetadata: {
60+
instanceSize: 'VERY BIG',
61+
metricsType: 'replicaSet',
62+
} as any,
63+
}
64+
);
65+
}
66+
67+
afterEach(function () {
68+
sandbox.reset();
69+
});
70+
71+
describe('rolling indexes', function () {
72+
it('should create a rolling index and show it in the table', async function () {
73+
const result = await render();
74+
75+
await waitFor(() => {
76+
expect(result.plugin.store.getState().regularIndexes).to.have.property(
77+
'status',
78+
'READY'
79+
);
80+
});
81+
82+
sandbox.stub(atlasService, 'automationAgentAwait').resolves({
83+
response: [
84+
{
85+
status: 'rolling build',
86+
indexName: 'field_a_1',
87+
indexProperties: {},
88+
indexType: { label: 'regular' },
89+
keys: [{ name: 'field_a', value: '1' }],
90+
},
91+
],
92+
});
93+
94+
/** Create rolling index */
95+
96+
userEvent.click(screen.getByRole('button', { name: 'Create Index' }));
97+
98+
userEvent.type(screen.getByLabelText('Index fields'), 'field_a');
99+
userEvent.click(screen.getByRole('option', { name: 'Field: "field_a"' }));
100+
101+
userEvent.click(screen.getByText('Select a type'));
102+
userEvent.click(screen.getByText('1 (asc)'));
103+
104+
userEvent.click(screen.getByTestId('create-index-modal-toggle-options'));
105+
106+
userEvent.click(
107+
screen.getByRole('checkbox', { name: 'Build in rolling process' }),
108+
undefined,
109+
{ skipPointerEventsCheck: true }
110+
);
111+
112+
userEvent.click(
113+
within(screen.getByTestId('create-index-modal')).getByRole('button', {
114+
name: 'Create Index',
115+
})
116+
);
117+
118+
const inProgressIndex =
119+
result.plugin.store.getState().regularIndexes.inProgressIndexes[0];
120+
121+
expect(inProgressIndex).to.exist;
122+
123+
/** Wait for the row to appear with in-progress bagde first */
124+
125+
expect(
126+
within(screen.getByTestId('indexes-row-field_a_1')).getByText(
127+
'In Progress'
128+
)
129+
).to.exist;
130+
131+
/** Now wait for the rolling index to show up */
132+
133+
await waitFor(() => {
134+
expect(
135+
within(screen.getByTestId('indexes-row-field_a_1')).getByText(
136+
'Building'
137+
)
138+
).to.exist;
139+
});
140+
141+
/** At some point rolling index timeout check fires, but nothing changes */
142+
143+
result.plugin.store.dispatch(
144+
rollingIndexTimeoutCheck(inProgressIndex.id)
145+
);
146+
147+
expect(
148+
within(screen.getByTestId('indexes-row-field_a_1')).getByText(
149+
'Building'
150+
)
151+
).to.exist;
152+
});
153+
});
154+
155+
it('should remove in progress rolling index on a timeout', async function () {
156+
const result = await render();
157+
158+
await waitFor(() => {
159+
expect(result.plugin.store.getState().regularIndexes).to.have.property(
160+
'status',
161+
'READY'
162+
);
163+
});
164+
165+
/** This time let's just do all the setup with actions directly, we tested UI above */
166+
167+
const inProgressIndex = prepareInProgressIndex('test', {
168+
name: 'test_index',
169+
spec: {},
170+
});
171+
172+
result.plugin.store.dispatch(indexCreationStarted(inProgressIndex));
173+
174+
expect(screen.getByTestId('indexes-row-test_index')).to.exist;
175+
176+
/** Timeout check "fired" before index was returned from the API, index is removed */
177+
178+
result.plugin.store.dispatch(rollingIndexTimeoutCheck('test'));
179+
180+
expect(() => screen.getByTestId('indexes-row-test_index')).to.throw();
181+
});
182+
});

packages/compass-indexes/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider';
1919
import { IndexesTabTitle } from './plugin-title';
2020
import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider';
2121

22-
const CompassIndexesHadronPlugin = registerHadronPlugin(
22+
export const CompassIndexesHadronPlugin = registerHadronPlugin(
2323
{
2424
name: 'CompassIndexes',
2525
component: function IndexesProvider({ children }) {

packages/compass-indexes/src/modules/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export type IndexesExtraArgs = {
7272
connectionInfoRef: ConnectionInfoRef;
7373
collection: Collection;
7474
rollingIndexesService: RollingIndexesService;
75+
pollingIntervalRef: {
76+
regularIndexes: ReturnType<typeof setInterval> | null;
77+
searchIndexes: ReturnType<typeof setInterval> | null;
78+
};
7579
};
7680
export type IndexesThunkDispatch<A extends Action = AnyAction> = ThunkDispatch<
7781
RootState,

packages/compass-indexes/src/modules/regular-indexes.spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,6 @@ describe('regular-indexes module', function () {
467467

468468
it('starts and stops the polling', async function () {
469469
const pollInterval = 5000;
470-
const tabId = 'my-tab';
471470

472471
const collection = createMockCollection();
473472

@@ -498,7 +497,7 @@ describe('regular-indexes module', function () {
498497
// initial load
499498
expect(indexesStub.callCount).to.equal(1);
500499

501-
store.dispatch(startPollingRegularIndexes(tabId));
500+
store.dispatch(startPollingRegularIndexes());
502501

503502
// poll
504503
clock.tick(pollInterval);
@@ -513,15 +512,15 @@ describe('regular-indexes module', function () {
513512
await waitForStatus('READY');
514513

515514
// stop
516-
store.dispatch(stopPollingRegularIndexes(tabId));
515+
store.dispatch(stopPollingRegularIndexes());
517516

518517
// no more polling
519518
clock.tick(pollInterval);
520519
expect(indexesStub.callCount).to.equal(3);
521520
await waitForStatus('READY');
522521

523522
// open again
524-
store.dispatch(startPollingRegularIndexes(tabId));
523+
store.dispatch(startPollingRegularIndexes());
525524

526525
// won't execute immediately
527526
expect(indexesStub.callCount).to.equal(3);
@@ -540,7 +539,7 @@ describe('regular-indexes module', function () {
540539
await waitForStatus('READY');
541540

542541
// clean up
543-
store.dispatch(stopPollingRegularIndexes(tabId));
542+
store.dispatch(stopPollingRegularIndexes());
544543

545544
expect(collection.fetch.callCount).to.equal(0);
546545
});

packages/compass-indexes/src/modules/regular-indexes.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ export type RollingIndex = Partial<AtlasIndexStats> &
5252
'indexName' | 'indexType' | 'keys'
5353
>;
5454

55-
const prepareInProgressIndex = (
55+
/**
56+
* @internal exported only for testing
57+
*/
58+
export const prepareInProgressIndex = (
5659
id: string,
5760
{
5861
name,
@@ -184,15 +187,11 @@ export default function reducer(
184187
action: AnyAction
185188
): State {
186189
if (isAction<IndexesOpenedAction>(action, ActionTypes.IndexesOpened)) {
187-
return {
188-
...state,
189-
};
190+
return state;
190191
}
191192

192193
if (isAction<IndexesClosedAction>(action, ActionTypes.IndexesClosed)) {
193-
return {
194-
...state,
195-
};
194+
return state;
196195
}
197196

198197
if (
@@ -453,37 +452,37 @@ export const pollRegularIndexes = (): IndexesThunkAction<
453452

454453
export const POLLING_INTERVAL = 5000;
455454

456-
const pollIntervalByTabId = new Map<string, ReturnType<typeof setInterval>>();
457-
458-
export const startPollingRegularIndexes = (
459-
tabId: string
460-
): IndexesThunkAction<void, FetchIndexesActions> => {
461-
return function (dispatch) {
462-
if (pollIntervalByTabId.has(tabId)) {
455+
export const startPollingRegularIndexes = (): IndexesThunkAction<
456+
void,
457+
FetchIndexesActions
458+
> => {
459+
return function (dispatch, _getState, { pollingIntervalRef }) {
460+
if (pollingIntervalRef.regularIndexes !== null) {
463461
return;
464462
}
465-
466-
pollIntervalByTabId.set(
467-
tabId,
468-
setInterval(() => {
469-
void dispatch(pollRegularIndexes());
470-
}, POLLING_INTERVAL)
471-
);
463+
pollingIntervalRef.regularIndexes = setInterval(() => {
464+
void dispatch(pollRegularIndexes());
465+
}, POLLING_INTERVAL);
472466
};
473467
};
474468

475-
export const stopPollingRegularIndexes = (tabId: string) => {
476-
return () => {
477-
if (!pollIntervalByTabId.has(tabId)) {
469+
export const stopPollingRegularIndexes = (): IndexesThunkAction<
470+
void,
471+
never
472+
> => {
473+
return (_dispatch, _getState, { pollingIntervalRef }) => {
474+
if (pollingIntervalRef.regularIndexes === null) {
478475
return;
479476
}
480-
481-
clearInterval(pollIntervalByTabId.get(tabId));
482-
pollIntervalByTabId.delete(tabId);
477+
clearInterval(pollingIntervalRef.regularIndexes);
478+
pollingIntervalRef.regularIndexes = null;
483479
};
484480
};
485481

486-
const indexCreationStarted = (
482+
/**
483+
* @internal exported only for testing
484+
*/
485+
export const indexCreationStarted = (
487486
inProgressIndex: InProgressIndex
488487
): IndexCreationStartedAction => ({
489488
type: ActionTypes.IndexCreationStarted,
@@ -506,6 +505,18 @@ const indexCreationFailed = (
506505
error,
507506
});
508507

508+
/**
509+
* @internal exported only for testing
510+
*/
511+
export const rollingIndexTimeoutCheck = (
512+
indexId: string
513+
): RollingIndexTimeoutCheckAction => {
514+
return {
515+
type: ActionTypes.RollingIndexTimeoutCheck,
516+
indexId,
517+
};
518+
};
519+
509520
export function createRegularIndex(
510521
inProgressIndexId: string,
511522
spec: Record<string, IndexDirection>,
@@ -562,11 +573,8 @@ export function createRegularIndex(
562573
// See action description for details
563574
if (isRollingIndexBuild) {
564575
setTimeout(() => {
565-
dispatch({
566-
type: ActionTypes.RollingIndexTimeoutCheck,
567-
indexId: inProgressIndexId,
568-
});
569-
}, POLLING_INTERVAL * 3);
576+
dispatch(rollingIndexTimeoutCheck(inProgressIndexId));
577+
}, POLLING_INTERVAL * 3).unref?.();
570578
}
571579
// Start a new fetch so that the newly added index's details can be
572580
// loaded. indexCreationSucceeded() will remove the in-progress one, but

0 commit comments

Comments
 (0)