Skip to content

Commit 98e708c

Browse files
khelanmodiCopilot
andcommitted
feat(collectionView): add Index Management tab
Adds an Indexes tab inside the existing CollectionView (between Results and Query Insights) for managing collection indexes: - Indexes tab in CollectionView (no new webview, no new command, no context-menu entry) - Top-toolbar Create Index button (replaces Find Query on the Indexes tab) - Find Query editor hidden on the Indexes tab - Index table: Name, Type (color pill), Fields, Memory, Usage (tooltip), Created, Notes, Actions (Delete, Hide/Unhide) - Create Index dialog: field picker (SchemaStore), per-field asc/desc, type selector with type-specific options, large-collection warning, validation - Footer with totals; ConfirmDialog for destructive delete - Reuses Fluent UI v9 + existing theme tokens; no new runtime dependencies Backend integration: all UI <-> backend traffic flows through src/webviews/documentdb/indexView/indexViewRouter.ts. Every procedure has a BACKEND INTEGRATION POINT comment block for the backend engineer documenting expected shape, currently-called ClustersClient method, and follow-ups. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 815e5e0 commit 98e708c

16 files changed

Lines changed: 1618 additions & 55 deletions

File tree

l10n/bundle.l10n.json

Lines changed: 58 additions & 0 deletions
Large diffs are not rendered by default.

src/webviews/_integration/appRouter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { openSurvey, promptAfterActionEventually } from '../../utils/survey';
3232
import { UsageImpact } from '../../utils/surveyTypes';
3333
import { collectionsViewRouter as collectionViewRouter } from '../documentdb/collectionView/collectionViewRouter';
3434
import { documentsViewRouter as documentViewRouter } from '../documentdb/documentView/documentsViewRouter';
35+
import { indexViewRouter } from '../documentdb/indexView/indexViewRouter';
3536
import { WEBVIEW_CONFIG } from './configuration';
3637
import { publicProcedure, publicProcedureWithTelemetry, router, type WithTelemetry } from './trpc';
3738

@@ -188,6 +189,7 @@ export const appRouter = router({
188189
mongoClusters: {
189190
documentView: documentViewRouter,
190191
collectionView: collectionViewRouter,
192+
indexView: indexViewRouter,
191193
},
192194
});
193195

src/webviews/documentdb/collectionView/CollectionView.tsx

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useTrpcClient } from '../../_integration/useTrpcClient';
1313
import { Announcer } from '../../components/accessibility';
1414
import { useSelectiveContextMenuPrevention } from '../../components/useSelectiveContextMenuPrevention';
1515
import { setCompletionContext } from '../../query-language-support';
16+
import { IndexesTab } from '../indexView/IndexesTab';
1617
import './collectionView.scss';
1718
import {
1819
CollectionViewContext,
@@ -123,7 +124,7 @@ export const CollectionView = (): JSX.Element => {
123124
const [currentQueryResults, setCurrentQueryResults] = useState<QueryResults>();
124125

125126
// Track which tab is currently active
126-
const [selectedTab, setSelectedTab] = useState<'tab_result' | 'tab_queryInsights'>('tab_result');
127+
const [selectedTab, setSelectedTab] = useState<'tab_result' | 'tab_indexes' | 'tab_queryInsights'>('tab_result');
127128

128129
// keep Refs updated with the current state
129130
const currentQueryResultsRef = useRef(currentQueryResults);
@@ -566,55 +567,57 @@ export const CollectionView = (): JSX.Element => {
566567
/>
567568

568569
<div className="toolbarMainView">
569-
<ToolbarMainView />
570+
<ToolbarMainView selectedTab={selectedTab} />
570571
</div>
571572

572-
<QueryEditor
573-
onExecuteRequest={() => {
574-
// Get all query values from the editor at once
575-
const query = currentContext.queryEditor?.getCurrentQuery() ?? {
576-
filter: '{ }',
577-
project: '{ }',
578-
sort: '{ }',
579-
skip: 0,
580-
limit: 0,
581-
};
582-
583-
setCurrentContext((prev) => ({
584-
...prev,
585-
activeQuery: {
586-
...prev.activeQuery,
587-
queryText: query.filter, // deprecated: kept in sync with filter
588-
filter: query.filter,
589-
project: query.project,
590-
sort: query.sort,
591-
skip: query.skip,
592-
limit: query.limit,
593-
pageNumber: 1,
594-
executionIntent: 'initial',
595-
},
596-
}));
597-
598-
trpcClient.common.reportEvent
599-
.mutate({
600-
eventName: 'executeQuery',
601-
properties: {
602-
ui: 'shortcut',
573+
{selectedTab !== 'tab_indexes' && (
574+
<QueryEditor
575+
onExecuteRequest={() => {
576+
// Get all query values from the editor at once
577+
const query = currentContext.queryEditor?.getCurrentQuery() ?? {
578+
filter: '{ }',
579+
project: '{ }',
580+
sort: '{ }',
581+
skip: 0,
582+
limit: 0,
583+
};
584+
585+
setCurrentContext((prev) => ({
586+
...prev,
587+
activeQuery: {
588+
...prev.activeQuery,
589+
queryText: query.filter, // deprecated: kept in sync with filter
590+
filter: query.filter,
591+
project: query.project,
592+
sort: query.sort,
593+
skip: query.skip,
594+
limit: query.limit,
595+
pageNumber: 1,
596+
executionIntent: 'initial',
603597
},
604-
measurements: {
605-
queryLenth: query.filter.length,
606-
},
607-
})
608-
.catch((error) => {
609-
console.debug('Failed to report an event:', error);
610-
});
611-
}}
612-
/>
598+
}));
599+
600+
trpcClient.common.reportEvent
601+
.mutate({
602+
eventName: 'executeQuery',
603+
properties: {
604+
ui: 'shortcut',
605+
},
606+
measurements: {
607+
queryLenth: query.filter.length,
608+
},
609+
})
610+
.catch((error) => {
611+
console.debug('Failed to report an event:', error);
612+
});
613+
}}
614+
/>
615+
)}
613616

614617
<TabList
615618
selectedValue={selectedTab}
616619
onTabSelect={(_event, data) => {
617-
const newTab = data.value as 'tab_result' | 'tab_queryInsights';
620+
const newTab = data.value as 'tab_result' | 'tab_indexes' | 'tab_queryInsights';
618621

619622
// Report tab switching telemetry
620623
trpcClient.common.reportEvent
@@ -636,6 +639,9 @@ export const CollectionView = (): JSX.Element => {
636639
<Tab id="tab.results" value="tab_result">
637640
Results
638641
</Tab>
642+
<Tab id="tab.indexes" value="tab_indexes">
643+
{l10n.t('Indexes')}
644+
</Tab>
639645
<Tab id="tab.queryInsights" value="tab_queryInsights">
640646
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
641647
Query Insights
@@ -690,6 +696,8 @@ export const CollectionView = (): JSX.Element => {
690696
</>
691697
)}
692698

699+
{selectedTab === 'tab_indexes' && <IndexesTab collectionName={configuration.collectionName} />}
700+
693701
{selectedTab === 'tab_queryInsights' && <QueryInsightsMain />}
694702
</div>
695703
</CollectionViewContext.Provider>

src/webviews/documentdb/collectionView/components/toolbar/ToolbarMainView.tsx

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
useOverflowMenu,
2323
} from '@fluentui/react-components';
2424
import {
25+
AddRegular,
2526
ArrowClockwiseRegular,
2627
ArrowExportRegular,
2728
ArrowImportRegular,
@@ -38,21 +39,31 @@ import { useConfiguration } from '@microsoft/vscode-ext-react-webview';
3839
import * as l10n from '@vscode/l10n';
3940
import { useContext, type JSX } from 'react';
4041
import { useTrpcClient } from '../../../../_integration/useTrpcClient';
42+
import { OPEN_CREATE_INDEX_EVENT } from '../../../indexView/constants';
4143
import { CollectionViewContext } from '../../collectionViewContext';
4244
import { type CollectionViewWebviewConfigurationType } from '../../collectionViewController';
4345
import { useHideScrollbarsDuringResize } from '../../hooks/useHideScrollbarsDuringResize';
4446
import { ToolbarDividerTransparent } from './ToolbarDividerTransparent';
4547

46-
export const ToolbarMainView = (): JSX.Element => {
48+
export interface ToolbarMainViewProps {
49+
/**
50+
* Which CollectionView tab is currently active. The primary toolbar
51+
* swaps its leading action button to match (Find Query on Results /
52+
* Query Insights, Create Index on Indexes).
53+
*/
54+
selectedTab?: 'tab_result' | 'tab_indexes' | 'tab_queryInsights';
55+
}
56+
57+
export const ToolbarMainView = ({ selectedTab }: ToolbarMainViewProps): JSX.Element => {
4758
return (
4859
<>
49-
<ToolbarQueryOperations />
60+
<ToolbarQueryOperations selectedTab={selectedTab} />
5061
<ToolbarSecondaryActions />
5162
</>
5263
);
5364
};
5465

55-
const ToolbarQueryOperations = (): JSX.Element => {
66+
const ToolbarQueryOperations = ({ selectedTab }: ToolbarMainViewProps): JSX.Element => {
5667
/**
5768
* Use the `useTrpcClient` hook to get the tRPC client
5869
*/
@@ -160,15 +171,32 @@ const ToolbarQueryOperations = (): JSX.Element => {
160171

161172
return (
162173
<Toolbar size="small" checkedValues={checkedValues} onCheckedValueChange={handleCheckedValueChange}>
163-
<ToolbarButton
164-
aria-label={l10n.t('Execute the find query')}
165-
disabled={currentContext.isLoading}
166-
icon={<PlayRegular />}
167-
onClick={handleExecuteQuery}
168-
appearance="primary"
169-
>
170-
{l10n.t('Find Query')}
171-
</ToolbarButton>
174+
{selectedTab === 'tab_indexes' ? (
175+
// On the Indexes tab the primary action is creating a new
176+
// index. We dispatch a window-level CustomEvent that the
177+
// IndexesTab component listens for; this keeps the toolbar
178+
// free of any direct coupling to the IndexesTab internals.
179+
<ToolbarButton
180+
aria-label={l10n.t('Create a new index')}
181+
icon={<AddRegular />}
182+
appearance="primary"
183+
onClick={() => {
184+
window.dispatchEvent(new CustomEvent(OPEN_CREATE_INDEX_EVENT));
185+
}}
186+
>
187+
{l10n.t('Create Index')}
188+
</ToolbarButton>
189+
) : (
190+
<ToolbarButton
191+
aria-label={l10n.t('Execute the find query')}
192+
disabled={currentContext.isLoading}
193+
icon={<PlayRegular />}
194+
onClick={handleExecuteQuery}
195+
appearance="primary"
196+
>
197+
{l10n.t('Find Query')}
198+
</ToolbarButton>
199+
)}
172200

173201
<ToolbarDividerTransparent />
174202

0 commit comments

Comments
 (0)