Skip to content

Commit 1daa01e

Browse files
feat(compass-indexes): adds a signal for notifying when there are too many indexes (#4575)
1 parent 070be45 commit 1daa01e

File tree

7 files changed

+180
-110
lines changed

7 files changed

+180
-110
lines changed

package-lock.json

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

packages/compass-indexes/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@
9090
"react": "^17.0.2",
9191
"react-dom": "^17.0.2",
9292
"react-redux": "^8.0.5",
93-
"react-virtualized-auto-sizer": "^1.0.6",
9493
"redux": "^4.2.1",
9594
"redux-thunk": "^2.4.1",
9695
"semver": "^5.4.1",

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ const renderIndexList = (
106106
onDeleteIndex={() => {}}
107107
onHideIndex={() => {}}
108108
onUnhideIndex={() => {}}
109-
scrollHeight={400}
110109
{...props}
111110
/>
112111
);

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

Lines changed: 112 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
spacing,
1010
palette,
1111
IndexKeysBadge,
12+
KeylineCard,
13+
useDOMRect,
1214
} from '@mongodb-js/compass-components';
1315

1416
import TypeField from './type-field';
@@ -69,10 +71,19 @@ const tableStyles = css({
6971
},
7072
});
7173

74+
const cardStyles = css({
75+
padding: spacing[3],
76+
});
77+
78+
const spaceProviderStyles = css({
79+
flex: 1,
80+
position: 'relative',
81+
overflow: 'hidden',
82+
});
83+
7284
type IndexesTableProps = {
7385
indexes: IndexDefinition[];
7486
canModifyIndex: boolean;
75-
scrollHeight: number;
7687
serverVersion: string;
7788
onSortTable: (column: SortColumn, direction: SortDirection) => void;
7889
onDeleteIndex: (index: IndexDefinition) => void;
@@ -83,14 +94,14 @@ type IndexesTableProps = {
8394
export const IndexesTable: React.FunctionComponent<IndexesTableProps> = ({
8495
indexes,
8596
canModifyIndex,
86-
scrollHeight,
8797
serverVersion,
8898
onSortTable,
8999
onDeleteIndex,
90100
onHideIndex,
91101
onUnhideIndex,
92102
}) => {
93-
const containerRef = useRef<HTMLDivElement>(null);
103+
const cardRef = useRef<HTMLDivElement>(null);
104+
const [rectProps, { height: availableHeightInContainer }] = useDOMRect();
94105
const columns = useMemo(() => {
95106
const sortColumns: SortColumn[] = [
96107
'Name and Definition',
@@ -121,80 +132,111 @@ export const IndexesTable: React.FunctionComponent<IndexesTableProps> = ({
121132

122133
useEffect(() => {
123134
/**
124-
* For table header to be sticky, the wrapper element of table needs to have a height.
125-
* LG wraps table in a div at multiple levels, so height can not be direclty applied to the wrapper we use.
135+
* For table header to be sticky, the wrapper element of table needs to have
136+
* a height. LG wraps table in a div at multiple levels, so height can not
137+
* be applied directly to the wrapper we have in this markup which is why we
138+
* look for the parent element to apply the height.
126139
*/
127-
const container =
128-
containerRef.current?.getElementsByTagName('table')[0]?.parentElement;
129-
if (container) {
130-
container.style.height = `${scrollHeight}px`;
140+
const table = cardRef.current?.getElementsByTagName('table')[0];
141+
const tableParent = table?.parentElement;
142+
143+
if (table && tableParent) {
144+
// We add a top and bottom padding of spacing[3] and our root container
145+
// has a bottom margin of spacing[3] which is why the actual usable
146+
// height of the container is less than what we get here
147+
const heightWithoutSpacing = availableHeightInContainer - spacing[3] * 3;
148+
149+
// This will be the height of the table. We take whichever is the max of
150+
// the actual table height vs the half of the height available to make
151+
// sure that our table does not always render in a super small keyline
152+
// card when there are only a few rows in the table.
153+
const tableHeight = Math.max(
154+
table.clientHeight,
155+
heightWithoutSpacing / 2
156+
);
157+
158+
// When we have enough space available to render the table, we simply want
159+
// our keyline card to have a height as much as that of the table content
160+
const tableParentHeight = Math.max(
161+
0,
162+
Math.min(tableHeight, heightWithoutSpacing)
163+
);
164+
tableParent.style.height = `${tableParentHeight}px`;
131165
}
132-
}, [scrollHeight]);
166+
}, [availableHeightInContainer]);
133167

134168
return (
135-
// LG table does not forward ref
136-
<div ref={containerRef}>
137-
<Table
138-
className={tableStyles}
139-
data={indexes}
140-
columns={columns}
141-
data-testid="indexes-list"
142-
aria-label="Indexes List Table"
143-
>
144-
{({ datum: index }) => (
145-
<Row
146-
key={index.name}
147-
data-testid={`index-row-${index.name}`}
148-
className={rowStyles}
149-
>
150-
<Cell data-testid="index-name-field" className={cellStyles}>
151-
{index.name}
152-
</Cell>
153-
<Cell data-testid="index-type-field" className={cellStyles}>
154-
<TypeField type={index.type} extra={index.extra} />
155-
</Cell>
156-
<Cell data-testid="index-size-field" className={cellStyles}>
157-
<SizeField size={index.size} relativeSize={index.relativeSize} />
158-
</Cell>
159-
<Cell data-testid="index-usage-field" className={cellStyles}>
160-
<UsageField usage={index.usageCount} since={index.usageSince} />
161-
</Cell>
162-
<Cell data-testid="index-property-field" className={cellStyles}>
163-
<PropertyField
164-
cardinality={index.cardinality}
165-
extra={index.extra}
166-
properties={index.properties}
167-
/>
168-
</Cell>
169-
{/* Index actions column is conditional */}
170-
{canModifyIndex && (
171-
<Cell data-testid="index-actions-field" className={cellStyles}>
172-
{index.name !== '_id_' && index.extra.status !== 'inprogress' && (
173-
<div
174-
className={cx(indexActionsCellStyles, 'index-actions-cell')}
175-
>
176-
<IndexActions
177-
index={index}
178-
serverVersion={serverVersion}
179-
onDeleteIndex={onDeleteIndex}
180-
onHideIndex={onHideIndex}
181-
onUnhideIndex={onUnhideIndex}
182-
></IndexActions>
183-
</div>
184-
)}
169+
<div className={spaceProviderStyles} {...rectProps}>
170+
<KeylineCard ref={cardRef} data-testid="indexes" className={cardStyles}>
171+
<Table
172+
className={tableStyles}
173+
data={indexes}
174+
columns={columns}
175+
data-testid="indexes-list"
176+
aria-label="Indexes List Table"
177+
>
178+
{({ datum: index }) => (
179+
<Row
180+
key={index.name}
181+
data-testid={`index-row-${index.name}`}
182+
className={rowStyles}
183+
>
184+
<Cell data-testid="index-name-field" className={cellStyles}>
185+
{index.name}
186+
</Cell>
187+
<Cell data-testid="index-type-field" className={cellStyles}>
188+
<TypeField type={index.type} extra={index.extra} />
189+
</Cell>
190+
<Cell data-testid="index-size-field" className={cellStyles}>
191+
<SizeField
192+
size={index.size}
193+
relativeSize={index.relativeSize}
194+
/>
195+
</Cell>
196+
<Cell data-testid="index-usage-field" className={cellStyles}>
197+
<UsageField usage={index.usageCount} since={index.usageSince} />
185198
</Cell>
186-
)}
187-
<Row>
188-
<Cell
189-
className={cx(nestedRowCellStyles, cellStyles)}
190-
colSpan={canModifyIndex ? 6 : 5}
191-
>
192-
<IndexKeysBadge keys={index.fields} />
199+
<Cell data-testid="index-property-field" className={cellStyles}>
200+
<PropertyField
201+
cardinality={index.cardinality}
202+
extra={index.extra}
203+
properties={index.properties}
204+
/>
193205
</Cell>
206+
{/* Index actions column is conditional */}
207+
{canModifyIndex && (
208+
<Cell data-testid="index-actions-field" className={cellStyles}>
209+
{index.name !== '_id_' &&
210+
index.extra.status !== 'inprogress' && (
211+
<div
212+
className={cx(
213+
indexActionsCellStyles,
214+
'index-actions-cell'
215+
)}
216+
>
217+
<IndexActions
218+
index={index}
219+
serverVersion={serverVersion}
220+
onDeleteIndex={onDeleteIndex}
221+
onHideIndex={onHideIndex}
222+
onUnhideIndex={onUnhideIndex}
223+
></IndexActions>
224+
</div>
225+
)}
226+
</Cell>
227+
)}
228+
<Row>
229+
<Cell
230+
className={cx(nestedRowCellStyles, cellStyles)}
231+
colSpan={canModifyIndex ? 6 : 5}
232+
>
233+
<IndexKeysBadge keys={index.fields} />
234+
</Cell>
235+
</Row>
194236
</Row>
195-
</Row>
196-
)}
197-
</Table>
237+
)}
238+
</Table>
239+
</KeylineCard>
198240
</div>
199241
);
200242
};

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const renderIndexesToolbar = (
1414

1515
render(
1616
<IndexesToolbar
17+
hasTooManyIndexes={false}
1718
errorMessage={null}
1819
isReadonly={false}
1920
isReadonlyView={false}
@@ -174,4 +175,23 @@ describe('IndexesToolbar Component', function () {
174175
expect(onRefreshIndexesSpy).to.have.been.calledOnce;
175176
});
176177
});
178+
179+
describe('when there are too many indexes', function () {
180+
it('should render insights for too many indexes', function () {
181+
renderIndexesToolbar({
182+
hasTooManyIndexes: true,
183+
});
184+
expect(() => screen.getByTestId('insight-badge-button')).to.not.throw;
185+
});
186+
187+
context('and when there is an error', function () {
188+
it('should not render insights', function () {
189+
renderIndexesToolbar({
190+
hasTooManyIndexes: true,
191+
errorMessage: 'Something bad happened',
192+
});
193+
expect(() => screen.getByTestId('insight-badge-button')).to.throw;
194+
});
195+
});
196+
});
177197
});

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

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import {
99
spacing,
1010
Icon,
1111
SpinLoader,
12+
SignalPopover,
1213
} from '@mongodb-js/compass-components';
1314
import type AppRegistry from 'hadron-app-registry';
15+
import { usePreference } from 'compass-preferences-model';
1416

1517
const containerStyles = css({
1618
margin: `${spacing[3]}px 0`,
@@ -20,7 +22,8 @@ const toolbarButtonsContainer = css({
2022
display: 'flex',
2123
flexDirection: 'row',
2224
gap: spacing[2],
23-
justifyContent: 'flex-end',
25+
justifyContent: 'flex-start',
26+
alignItems: 'center',
2427
});
2528

2629
const errorStyles = css({ marginTop: spacing[2] });
@@ -36,6 +39,7 @@ type IndexesToolbarProps = {
3639
isReadonly: boolean;
3740
isReadonlyView: boolean;
3841
isWritable: boolean;
42+
hasTooManyIndexes: boolean;
3943
localAppRegistry: AppRegistry;
4044
isRefreshing: boolean;
4145
writeStateDescription?: string;
@@ -51,9 +55,11 @@ export const IndexesToolbar: React.FunctionComponent<IndexesToolbarProps> = ({
5155
localAppRegistry,
5256
isRefreshing,
5357
writeStateDescription,
58+
hasTooManyIndexes,
5459
onRefreshIndexes,
5560
readOnly, // preferences readOnly.
5661
}) => {
62+
const showInsights = usePreference('showInsights', React) && !errorMessage;
5763
const onClickCreateIndex = useCallback(() => {
5864
localAppRegistry.emit('open-create-index-modal');
5965
}, [localAppRegistry]);
@@ -72,16 +78,6 @@ export const IndexesToolbar: React.FunctionComponent<IndexesToolbarProps> = ({
7278
{!isReadonlyView && (
7379
<div data-testid="indexes-toolbar">
7480
<div className={toolbarButtonsContainer}>
75-
<Button
76-
data-testid="refresh-indexes-button"
77-
disabled={isRefreshing}
78-
onClick={() => onRefreshIndexes()}
79-
variant="default"
80-
size="small"
81-
leftGlyph={refreshButtonIcon}
82-
>
83-
Refresh
84-
</Button>
8581
{showCreateIndexButton && (
8682
<Tooltip
8783
enabled={!isWritable}
@@ -112,6 +108,28 @@ export const IndexesToolbar: React.FunctionComponent<IndexesToolbarProps> = ({
112108
{writeStateDescription}
113109
</Tooltip>
114110
)}
111+
<Button
112+
data-testid="refresh-indexes-button"
113+
disabled={isRefreshing}
114+
onClick={() => onRefreshIndexes()}
115+
variant="default"
116+
size="small"
117+
leftGlyph={refreshButtonIcon}
118+
>
119+
Refresh
120+
</Button>
121+
{showInsights && hasTooManyIndexes && (
122+
<SignalPopover
123+
signals={{
124+
id: 'too-many-indexes',
125+
title: 'High number of indexes on collection',
126+
description:
127+
'Consider reviewing your indexes to remove any that are unnecessary. Learn more about this anti-pattern',
128+
learnMoreLink:
129+
'https://www.mongodb.com/docs/manual/core/data-model-operations/#indexes',
130+
}}
131+
/>
132+
)}
115133
</div>
116134
</div>
117135
)}

0 commit comments

Comments
 (0)