Revert "Index Advisor Tab on Execute Query (#2177)" (#2244)

This reverts commit abf4b3bd0f.

Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
This commit is contained in:
Nishtha Ahuja
2025-11-06 20:17:17 +05:30
committed by GitHub
parent bbdf0ce57e
commit 6b150dbfa0
12 changed files with 18 additions and 929 deletions

View File

@@ -1,8 +1,5 @@
import type { CompositePath, IndexingPolicy } from "@azure/cosmos";
import { FontIcon } from "@fluentui/react";
import {
Button,
Checkbox,
DataGrid,
DataGridBody,
DataGridCell,
@@ -11,45 +8,28 @@ import {
DataGridRow,
SelectTabData,
SelectTabEvent,
Spinner,
Tab,
TabList,
Table,
TableBody,
TableCell,
TableColumnDefinition,
TableHeader,
TableRow,
createTableColumn,
} from "@fluentui/react-components";
import { ArrowDownloadRegular, ChevronDown20Regular, ChevronRight20Regular, CopyRegular } from "@fluentui/react-icons";
import copy from "clipboard-copy";
import { ArrowDownloadRegular, CopyRegular } from "@fluentui/react-icons";
import { HttpHeaders } from "Common/Constants";
import MongoUtility from "Common/MongoUtility";
import { QueryMetrics } from "Contracts/DataModels";
import { QueryResults } from "Contracts/ViewModels";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import {
parseIndexMetrics,
renderImpactDots,
type IndexMetricsResponse,
} from "Explorer/Tabs/QueryTab/IndexAdvisorUtils";
import { IDocument } from "Explorer/Tabs/QueryTab/QueryTabComponent";
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
import React, { useCallback, useEffect, useState } from "react";
import { userContext } from "UserContext";
import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
import create from "zustand";
import { client } from "../../../Common/CosmosClient";
import { handleError } from "../../../Common/ErrorHandlingUtils";
import { sampleDataClient } from "../../../Common/SampleDataClient";
import copy from "clipboard-copy";
import React, { useCallback, useState } from "react";
import { ResultsViewProps } from "./QueryResultSection";
import { useIndexAdvisorStyles } from "./StylesAdvisor";
enum ResultsTabs {
Results = "results",
QueryStats = "queryStats",
IndexAdvisor = "indexadv",
}
const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => {
const styles = useQueryTabStyles();
/* eslint-disable react/prop-types */
@@ -543,331 +523,14 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
);
};
export interface IIndexMetric {
index: string;
impact: string;
section: "Included" | "Not Included" | "Header";
path?: string;
composite?: { path: string; order: string }[];
}
export const IndexAdvisorTab: React.FC<{
queryResults?: QueryResults;
queryEditorContent?: string;
databaseId?: string;
containerId?: string;
}> = ({ queryResults, queryEditorContent, databaseId, containerId }) => {
const style = useIndexAdvisorStyles();
const [loading, setLoading] = useState(false);
const [indexMetrics, setIndexMetrics] = useState<IndexMetricsResponse | null>(null);
const [showIncluded, setShowIncluded] = useState(true);
const [showNotIncluded, setShowNotIncluded] = useState(true);
const [selectedIndexes, setSelectedIndexes] = useState<IIndexMetric[]>([]);
const [selectAll, setSelectAll] = useState(false);
const [updateMessageShown, setUpdateMessageShown] = useState(false);
const [included, setIncludedIndexes] = useState<IIndexMetric[]>([]);
const [notIncluded, setNotIncludedIndexes] = useState<IIndexMetric[]>([]);
const [isUpdating, setIsUpdating] = useState(false);
const [justUpdatedPolicy, setJustUpdatedPolicy] = useState(false);
const indexingMetricsDocLink = "https://learn.microsoft.com/azure/cosmos-db/nosql/index-metrics";
const fetchIndexMetrics = async () => {
if (!queryEditorContent || !databaseId || !containerId) {
return;
}
setLoading(true);
const clearMessage = logConsoleProgress(`Querying items with IndexMetrics in container ${containerId}`);
try {
const querySpec = {
query: queryEditorContent,
};
// Use sampleDataClient for CopilotSampleDB, regular client for other databases
const cosmosClient = databaseId === "CopilotSampleDB" ? sampleDataClient() : client();
const sdkResponse = await cosmosClient
.database(databaseId)
.container(containerId)
.items.query(querySpec, {
populateIndexMetrics: true,
})
.fetchAll();
const parsedMetrics =
typeof sdkResponse.indexMetrics === "string" ? JSON.parse(sdkResponse.indexMetrics) : sdkResponse.indexMetrics;
setIndexMetrics(parsedMetrics);
} catch (error) {
handleError(error, "queryItemsWithIndexMetrics", `Error querying items from ${containerId}`);
} finally {
clearMessage();
setLoading(false);
}
};
// Fetch index metrics when query results change (i.e., when Execute Query is clicked)
useEffect(() => {
if (queryEditorContent && databaseId && containerId && queryResults) {
fetchIndexMetrics();
}
}, [queryResults]);
useEffect(() => {
if (!indexMetrics) {
return;
}
const { included, notIncluded } = parseIndexMetrics(indexMetrics);
setIncludedIndexes(included);
setNotIncludedIndexes(notIncluded);
if (justUpdatedPolicy) {
setJustUpdatedPolicy(false);
} else {
setUpdateMessageShown(false);
}
}, [indexMetrics]);
useEffect(() => {
const allSelected =
notIncluded.length > 0 && notIncluded.every((item) => selectedIndexes.some((s) => s.index === item.index));
setSelectAll(allSelected);
}, [selectedIndexes, notIncluded]);
const handleCheckboxChange = (indexObj: IIndexMetric, checked: boolean) => {
if (checked) {
setSelectedIndexes((prev) => [...prev, indexObj]);
} else {
setSelectedIndexes((prev) => prev.filter((item) => item.index !== indexObj.index));
}
};
const handleSelectAll = (checked: boolean) => {
setSelectAll(checked);
setSelectedIndexes(checked ? notIncluded : []);
};
const handleUpdatePolicy = async () => {
setIsUpdating(true);
try {
const containerRef = client().database(databaseId).container(containerId);
const { resource: containerDef } = await containerRef.read();
const newIncludedPaths = selectedIndexes
.filter((index) => !index.composite)
.map((index) => {
return {
path: index.path,
};
});
const newCompositeIndexes: CompositePath[][] = selectedIndexes
.filter((index) => Array.isArray(index.composite))
.map(
(index) =>
(index.composite as { path: string; order: string }[]).map((comp) => ({
path: comp.path,
order: comp.order === "descending" ? "descending" : "ascending",
})) as CompositePath[],
);
const updatedPolicy: IndexingPolicy = {
...containerDef.indexingPolicy,
includedPaths: [...(containerDef.indexingPolicy?.includedPaths || []), ...newIncludedPaths],
compositeIndexes: [...(containerDef.indexingPolicy?.compositeIndexes || []), ...newCompositeIndexes],
automatic: containerDef.indexingPolicy?.automatic ?? true,
indexingMode: containerDef.indexingPolicy?.indexingMode ?? "consistent",
excludedPaths: containerDef.indexingPolicy?.excludedPaths ?? [],
};
await containerRef.replace({
id: containerId,
partitionKey: containerDef.partitionKey,
indexingPolicy: updatedPolicy,
});
useIndexingPolicyStore.getState().setIndexingPolicyFor(containerId, updatedPolicy);
const selectedIndexSet = new Set(selectedIndexes.map((s) => s.index));
const updatedNotIncluded: typeof notIncluded = [];
const newlyIncluded: typeof included = [];
for (const item of notIncluded) {
if (selectedIndexSet.has(item.index)) {
newlyIncluded.push(item);
} else {
updatedNotIncluded.push(item);
}
}
const newIncluded = [...included, ...newlyIncluded];
const newNotIncluded = updatedNotIncluded;
setIncludedIndexes(newIncluded);
setNotIncludedIndexes(newNotIncluded);
setSelectedIndexes([]);
setSelectAll(false);
setUpdateMessageShown(true);
setJustUpdatedPolicy(true);
} catch (err) {
console.error("Failed to update indexing policy:", err);
} finally {
setIsUpdating(false);
}
};
const renderRow = (item: IIndexMetric, index: number) => {
const isHeader = item.section === "Header";
const isNotIncluded = item.section === "Not Included";
return (
<TableRow key={index}>
<TableCell colSpan={2}>
<div className={style.indexAdvisorGrid}>
{isNotIncluded ? (
<Checkbox
checked={selectedIndexes.some((selected) => selected.index === item.index)}
onChange={(_, data) => handleCheckboxChange(item, data.checked === true)}
/>
) : isHeader && item.index === "Not Included in Current Policy" && notIncluded.length > 0 ? (
<Checkbox checked={selectAll} onChange={(_, data) => handleSelectAll(data.checked === true)} />
) : (
<div className={style.indexAdvisorCheckboxSpacer}></div>
)}
{isHeader ? (
<span
style={{ cursor: "pointer" }}
onClick={() => {
if (item.index === "Included in Current Policy") {
setShowIncluded(!showIncluded);
} else if (item.index === "Not Included in Current Policy") {
setShowNotIncluded(!showNotIncluded);
}
}}
>
{item.index === "Included in Current Policy" ? (
showIncluded ? (
<ChevronDown20Regular />
) : (
<ChevronRight20Regular />
)
) : showNotIncluded ? (
<ChevronDown20Regular />
) : (
<ChevronRight20Regular />
)}
</span>
) : (
<div className={style.indexAdvisorChevronSpacer}></div>
)}
<div className={isHeader ? style.indexAdvisorRowBold : style.indexAdvisorRowNormal}>{item.index}</div>
<div className={isHeader ? style.indexAdvisorRowImpactHeader : style.indexAdvisorRowImpact}>
{!isHeader && item.impact}
</div>
<div>{!isHeader && renderImpactDots(item.impact)}</div>
</div>
</TableCell>
</TableRow>
);
};
const indexMetricItems = React.useMemo(() => {
const items: IIndexMetric[] = [];
items.push({ index: "Not Included in Current Policy", impact: "", section: "Header" });
if (showNotIncluded) {
notIncluded.forEach((item) => items.push({ ...item, section: "Not Included" }));
}
items.push({ index: "Included in Current Policy", impact: "", section: "Header" });
if (showIncluded) {
included.forEach((item) => items.push({ ...item, section: "Included" }));
}
return items;
}, [included, notIncluded, showIncluded, showNotIncluded]);
if (loading) {
return (
<div>
<Spinner
size="small"
style={
{
"--spinner-size": "16px",
"--spinner-thickness": "2px",
"--spinner-color": "#0078D4",
} as React.CSSProperties
}
/>
</div>
);
}
return (
<div>
<div className={style.indexAdvisorMessage}>
{updateMessageShown ? (
<>
<span className={style.indexAdvisorSuccessIcon}>
<FontIcon iconName="CheckMark" style={{ color: "white", fontSize: 12 }} />
</span>
<span>
Your indexing policy has been updated with the new included paths. You may review the changes in Scale &
Settings.
</span>
</>
) : (
<>
<span>
Index Advisor uses Indexing Metrics to suggest query paths that, when included in your indexing policy,
can improve the performance of this query by reducing RU costs and lowering latency.{" "}
<a href={indexingMetricsDocLink} target="_blank" rel="noopener noreferrer">
Learn more about Indexing Metrics
</a>
.{" "}
</span>
</>
)}
</div>
<div className={style.indexAdvisorTitle}>Indexes analysis</div>
<Table className={style.indexAdvisorTable}>
<TableHeader>
<TableRow>
<TableCell colSpan={2}>
<div className={style.indexAdvisorGrid}>
<div className={style.indexAdvisorCheckboxSpacer}></div>
<div className={style.indexAdvisorChevronSpacer}></div>
<div>Index</div>
<div>
<span style={{ whiteSpace: "nowrap" }}>Estimated Impact</span>
</div>
</div>
</TableCell>
</TableRow>
</TableHeader>
<TableBody>{indexMetricItems.map(renderRow)}</TableBody>
</Table>
{selectedIndexes.length > 0 && (
<div className={style.indexAdvisorButtonBar}>
{isUpdating ? (
<div className={style.indexAdvisorButtonSpinner}>
<Spinner size="tiny" />{" "}
</div>
) : (
<button onClick={handleUpdatePolicy} className={style.indexAdvisorButton}>
Update Indexing Policy with selected index(es)
</button>
)}
</div>
)}
</div>
);
};
export const ResultsView: React.FC<ResultsViewProps> = ({
isMongoDB,
queryResults,
executeQueryDocumentsPage,
queryEditorContent,
databaseId,
containerId,
}) => {
export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResults, executeQueryDocumentsPage }) => {
const styles = useQueryTabStyles();
const [activeTab, setActiveTab] = useState<ResultsTabs>(ResultsTabs.Results);
const onTabSelect = useCallback((event: SelectTabEvent, data: SelectTabData) => {
setActiveTab(data.value as ResultsTabs);
}, []);
return (
<div data-test="QueryTab/ResultsPane/ResultsView" className={styles.queryResultsTabPanel}>
<TabList selectedValue={activeTab} onTabSelect={onTabSelect}>
@@ -885,13 +548,6 @@ export const ResultsView: React.FC<ResultsViewProps> = ({
>
Query Stats
</Tab>
<Tab
data-test="QueryTab/ResultsPane/ResultsView/IndexAdvisorTab"
id={ResultsTabs.IndexAdvisor}
value={ResultsTabs.IndexAdvisor}
>
Index Advisor
</Tab>
</TabList>
<div className={styles.queryResultsTabContentContainer}>
{activeTab === ResultsTabs.Results && (
@@ -902,30 +558,7 @@ export const ResultsView: React.FC<ResultsViewProps> = ({
/>
)}
{activeTab === ResultsTabs.QueryStats && <QueryStatsTab queryResults={queryResults} />}
{activeTab === ResultsTabs.IndexAdvisor && (
<IndexAdvisorTab
queryResults={queryResults}
queryEditorContent={queryEditorContent}
databaseId={databaseId}
containerId={containerId}
/>
)}
</div>
</div>
);
};
export interface IndexingPolicyStore {
indexingPolicies: { [containerId: string]: IndexingPolicy };
setIndexingPolicyFor: (containerId: string, indexingPolicy: IndexingPolicy) => void;
}
export const useIndexingPolicyStore = create<IndexingPolicyStore>((set) => ({
indexingPolicies: {},
setIndexingPolicyFor: (containerId, indexingPolicy) =>
set((state) => ({
indexingPolicies: {
...state.indexingPolicies,
[containerId]: { ...indexingPolicy },
},
})),
}));