Skip to content

Commit a779283

Browse files
authored
feat(compass-indexes): add index build progress COMPASS-9495 (#7200)
1 parent d8a0d16 commit a779283

File tree

12 files changed

+310
-44
lines changed

12 files changed

+310
-44
lines changed

packages/compass-indexes/src/components/indexes/indexes.spec.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ describe('Indexes Component', function () {
190190
},
191191
],
192192
usageCount: 20,
193+
buildProgress: 0,
193194
},
194195
],
195196
inProgressIndexes: [
@@ -203,6 +204,7 @@ describe('Indexes Component', function () {
203204
},
204205
],
205206
status: 'inprogress',
207+
buildProgress: 0,
206208
},
207209
],
208210
error: undefined,
@@ -245,6 +247,7 @@ describe('Indexes Component', function () {
245247
},
246248
],
247249
usageCount: 20,
250+
buildProgress: 0,
248251
},
249252
],
250253
inProgressIndexes: [
@@ -259,6 +262,7 @@ describe('Indexes Component', function () {
259262
],
260263
status: 'failed',
261264
error: 'Error message',
265+
buildProgress: 0,
262266
},
263267
],
264268
error: undefined,

packages/compass-indexes/src/components/regular-indexes-table/in-progress-index-actions.spec.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('IndexActions Component', function () {
2626
index={{
2727
name: 'artist_id_index',
2828
status: 'inprogress',
29+
buildProgress: 0,
2930
}}
3031
onDeleteFailedIndexClick={onDeleteSpy}
3132
/>
@@ -41,6 +42,7 @@ describe('IndexActions Component', function () {
4142
index={{
4243
name: 'artist_id_index',
4344
status: 'failed',
45+
buildProgress: 0,
4446
}}
4547
onDeleteFailedIndexClick={onDeleteSpy}
4648
/>

packages/compass-indexes/src/components/regular-indexes-table/in-progress-index-actions.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import React, { useCallback, useMemo } from 'react';
22
import type { GroupedItemAction } from '@mongodb-js/compass-components';
3-
import { ItemActionGroup } from '@mongodb-js/compass-components';
3+
import { ItemActionGroup, css, spacing } from '@mongodb-js/compass-components';
44
import type { InProgressIndex } from '../../modules/regular-indexes';
55

66
type Index = {
77
name: string;
88
status: InProgressIndex['status'];
9+
buildProgress: number;
910
};
1011

12+
const indexActionsContainerStyles = css({
13+
display: 'flex',
14+
alignItems: 'center',
15+
justifyContent: 'flex-end',
16+
gap: spacing[200],
17+
});
18+
1119
type IndexActionsProps = {
1220
index: Index;
1321
onDeleteFailedIndexClick: (name: string) => void;
@@ -44,11 +52,13 @@ const IndexActions: React.FunctionComponent<IndexActionsProps> = ({
4452
);
4553

4654
return (
47-
<ItemActionGroup<IndexAction>
48-
data-testid="index-actions"
49-
actions={indexActions}
50-
onAction={onAction}
51-
></ItemActionGroup>
55+
<div className={indexActionsContainerStyles}>
56+
<ItemActionGroup<IndexAction>
57+
data-testid="index-actions"
58+
actions={indexActions}
59+
onAction={onAction}
60+
/>
61+
</div>
5262
);
5363
};
5464

packages/compass-indexes/src/components/regular-indexes-table/regular-index-actions.spec.tsx

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ import { spy } from 'sinon';
1010
import type { SinonSpy } from 'sinon';
1111

1212
import RegularIndexActions from './regular-index-actions';
13+
import type { RegularIndex } from '../../modules/regular-indexes';
14+
15+
const commonIndexProperties: RegularIndex = {
16+
name: 'artist_id_index',
17+
type: 'regular',
18+
cardinality: 'compound',
19+
properties: [],
20+
fields: [],
21+
extra: {},
22+
size: 0,
23+
relativeSize: 0,
24+
usageCount: 0,
25+
buildProgress: 0,
26+
};
1327

1428
describe('IndexActions Component', function () {
1529
let onDeleteSpy: SinonSpy;
@@ -24,10 +38,104 @@ describe('IndexActions Component', function () {
2438
onUnhideIndexSpy = spy();
2539
});
2640

41+
describe('build progress display', function () {
42+
it('does not display progress percentage when buildProgress is 0', function () {
43+
render(
44+
<RegularIndexActions
45+
index={{
46+
...commonIndexProperties,
47+
name: 'test_index',
48+
buildProgress: 0,
49+
}}
50+
serverVersion={'4.4.0'}
51+
onDeleteIndexClick={onDeleteSpy}
52+
onHideIndexClick={onHideIndexSpy}
53+
onUnhideIndexClick={onUnhideIndexSpy}
54+
/>
55+
);
56+
57+
// Should not show building spinner or percentage
58+
expect(() => screen.getByTestId('index-building-spinner')).to.throw(
59+
/Unable to find/
60+
);
61+
expect(() => screen.getByText(/Building \d+%/)).to.throw(
62+
/Unable to find/
63+
);
64+
});
65+
66+
it('displays progress percentage when buildProgress is 50% (0.5)', function () {
67+
render(
68+
<RegularIndexActions
69+
index={{
70+
...commonIndexProperties,
71+
name: 'test_index',
72+
buildProgress: 0.5,
73+
}}
74+
serverVersion={'4.4.0'}
75+
onDeleteIndexClick={onDeleteSpy}
76+
onHideIndexClick={onHideIndexSpy}
77+
onUnhideIndexClick={onUnhideIndexSpy}
78+
/>
79+
);
80+
81+
// Should show building spinner and percentage
82+
const buildingSpinner = screen.getByTestId('index-building-spinner');
83+
expect(buildingSpinner).to.exist;
84+
85+
const progressText = screen.getByText('Building... 50%');
86+
expect(progressText).to.exist;
87+
});
88+
89+
it('does not display progress percentage when buildProgress is 100% (1.0)', function () {
90+
render(
91+
<RegularIndexActions
92+
index={{
93+
...commonIndexProperties,
94+
name: 'test_index',
95+
buildProgress: 1.0,
96+
}}
97+
serverVersion={'4.4.0'}
98+
onDeleteIndexClick={onDeleteSpy}
99+
onHideIndexClick={onHideIndexSpy}
100+
onUnhideIndexClick={onUnhideIndexSpy}
101+
/>
102+
);
103+
104+
// Should not show building spinner or percentage when complete
105+
expect(() => screen.getByTestId('index-building-spinner')).to.throw;
106+
expect(() => screen.getByText(/Building\.\.\. \d+%/)).to.throw;
107+
});
108+
109+
it('displays cancel button when index is building', function () {
110+
render(
111+
<RegularIndexActions
112+
index={{
113+
...commonIndexProperties,
114+
name: 'building_index',
115+
buildProgress: 0.3,
116+
}}
117+
serverVersion={'4.4.0'}
118+
onDeleteIndexClick={onDeleteSpy}
119+
onHideIndexClick={onHideIndexSpy}
120+
onUnhideIndexClick={onUnhideIndexSpy}
121+
/>
122+
);
123+
124+
const cancelButton = screen.getByLabelText('Cancel Index building_index');
125+
expect(cancelButton).to.exist;
126+
expect(onDeleteSpy.callCount).to.equal(0);
127+
userEvent.click(cancelButton);
128+
expect(onDeleteSpy.callCount).to.equal(1);
129+
});
130+
});
131+
27132
it('renders delete button for a regular index', function () {
28133
render(
29134
<RegularIndexActions
30-
index={{ name: 'artist_id_index' }}
135+
index={{
136+
...commonIndexProperties,
137+
name: 'artist_id_index',
138+
}}
31139
serverVersion={'4.4.0'}
32140
onDeleteIndexClick={onDeleteSpy}
33141
onHideIndexClick={onHideIndexSpy}
@@ -52,6 +160,7 @@ describe('IndexActions Component', function () {
52160
render(
53161
<RegularIndexActions
54162
index={{
163+
...commonIndexProperties,
55164
name: 'artist_id_index',
56165
}}
57166
serverVersion={'4.4.0'}
@@ -75,6 +184,7 @@ describe('IndexActions Component', function () {
75184
render(
76185
<RegularIndexActions
77186
index={{
187+
...commonIndexProperties,
78188
name: 'artist_id_index',
79189
extra: { hidden: true },
80190
}}
@@ -103,6 +213,7 @@ describe('IndexActions Component', function () {
103213
render(
104214
<RegularIndexActions
105215
index={{
216+
...commonIndexProperties,
106217
name: 'artist_id_index',
107218
extra: { hidden: true },
108219
}}

packages/compass-indexes/src/components/regular-indexes-table/regular-index-actions.tsx

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
import semver from 'semver';
22
import React, { useCallback, useMemo } from 'react';
33
import type { GroupedItemAction } from '@mongodb-js/compass-components';
4-
import { css, ItemActionGroup } from '@mongodb-js/compass-components';
4+
import {
5+
css,
6+
ItemActionGroup,
7+
SpinLoader,
8+
Body,
9+
spacing,
10+
} from '@mongodb-js/compass-components';
11+
import type { RegularIndex } from '../../modules/regular-indexes';
512

613
const styles = css({
714
// Align actions with the end of the table
815
justifyContent: 'flex-end',
916
});
1017

11-
type Index = {
12-
name: string;
13-
extra?: {
14-
hidden?: boolean;
15-
};
16-
};
18+
const buildProgressStyles = css({
19+
display: 'flex',
20+
alignItems: 'center',
21+
justifyContent: 'flex-end',
22+
gap: spacing[200],
23+
});
1724

1825
type IndexActionsProps = {
19-
index: Index;
26+
index: RegularIndex;
2027
serverVersion: string;
2128
onDeleteIndexClick: (name: string) => void;
2229
onHideIndexClick: (name: string) => void;
@@ -44,33 +51,46 @@ const IndexActions: React.FunctionComponent<IndexActionsProps> = ({
4451
}) => {
4552
const indexActions: GroupedItemAction<IndexAction>[] = useMemo(() => {
4653
const actions: GroupedItemAction<IndexAction>[] = [];
54+
const buildProgress = index.buildProgress;
55+
const isBuilding = buildProgress > 0 && buildProgress < 1;
4756

48-
if (serverSupportsHideIndex(serverVersion)) {
49-
actions.push(
50-
index.extra?.hidden
51-
? {
52-
action: 'unhide',
53-
label: `Unhide Index ${index.name}`,
54-
tooltip: `Unhide Index`,
55-
icon: 'Visibility',
56-
}
57-
: {
58-
action: 'hide',
59-
label: `Hide Index ${index.name}`,
60-
tooltip: `Hide Index`,
61-
icon: 'VisibilityOff',
62-
}
63-
);
64-
}
57+
if (isBuilding) {
58+
// partially built
59+
actions.push({
60+
action: 'delete',
61+
label: `Cancel Index ${index.name}`,
62+
icon: 'XWithCircle',
63+
variant: 'destructive',
64+
});
65+
} else {
66+
// completed
67+
if (serverSupportsHideIndex(serverVersion)) {
68+
actions.push(
69+
index.extra?.hidden
70+
? {
71+
action: 'unhide',
72+
label: `Unhide Index ${index.name}`,
73+
tooltip: `Unhide Index`,
74+
icon: 'Visibility',
75+
}
76+
: {
77+
action: 'hide',
78+
label: `Hide Index ${index.name}`,
79+
tooltip: `Hide Index`,
80+
icon: 'VisibilityOff',
81+
}
82+
);
83+
}
6584

66-
actions.push({
67-
action: 'delete',
68-
label: `Drop Index ${index.name}`,
69-
icon: 'Trash',
70-
});
85+
actions.push({
86+
action: 'delete',
87+
label: `Drop Index ${index.name}`,
88+
icon: 'Trash',
89+
});
90+
}
7191

7292
return actions;
73-
}, [index, serverVersion]);
93+
}, [index.name, index.extra?.hidden, index.buildProgress, serverVersion]);
7494

7595
const onAction = useCallback(
7696
(action: IndexAction) => {
@@ -85,6 +105,21 @@ const IndexActions: React.FunctionComponent<IndexActionsProps> = ({
85105
[onDeleteIndexClick, onHideIndexClick, onUnhideIndexClick, index]
86106
);
87107

108+
const buildProgress = index.buildProgress;
109+
if (buildProgress > 0 && buildProgress < 1) {
110+
return (
111+
<div className={buildProgressStyles} data-testid="index-building-spinner">
112+
<Body>Building... {Math.trunc(buildProgress * 100)}%</Body>
113+
<SpinLoader size={16} title="Index build in progress" />
114+
<ItemActionGroup<IndexAction>
115+
data-testid="index-actions"
116+
actions={indexActions}
117+
onAction={onAction}
118+
/>
119+
</div>
120+
);
121+
}
122+
88123
return (
89124
<ItemActionGroup<IndexAction>
90125
data-testid="index-actions"

0 commit comments

Comments
 (0)