From 6b150dbfa0789e37ee491c99f015dfeb16fa62d9 Mon Sep 17 00:00:00 2001 From: Nishtha Ahuja <45535788+nishthaAhujaa@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:17:17 +0530 Subject: [PATCH] Revert "Index Advisor Tab on Execute Query (#2177)" (#2244) This reverts commit abf4b3bd0ffed0de4b87314a588036f5a05b01d6. Co-authored-by: nishthaAhujaa --- .../Settings/SettingsComponent.test.tsx | 47 --- .../Controls/Settings/SettingsComponent.tsx | 42 +- .../IndexingPolicyRefreshComponent.tsx | 4 +- .../SettingsComponent.test.tsx.snap | 48 --- .../Tabs/QueryTab/IndexAdvisorUtils.tsx | 107 ----- .../Tabs/QueryTab/QueryResultSection.tsx | 12 +- .../Tabs/QueryTab/QueryTabComponent.test.tsx | 3 +- .../Tabs/QueryTab/QueryTabComponent.tsx | 23 -- .../Tabs/QueryTab/ResultsView.test.tsx | 170 -------- src/Explorer/Tabs/QueryTab/ResultsView.tsx | 381 +----------------- src/Explorer/Tabs/QueryTab/StylesAdvisor.ts | 95 ----- .../Tabs/QueryTab/useQueryMetadataStore.ts | 15 - 12 files changed, 18 insertions(+), 929 deletions(-) delete mode 100644 src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx delete mode 100644 src/Explorer/Tabs/QueryTab/ResultsView.test.tsx delete mode 100644 src/Explorer/Tabs/QueryTab/StylesAdvisor.ts delete mode 100644 src/Explorer/Tabs/QueryTab/useQueryMetadataStore.ts diff --git a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx index 4421a5114..e6e57f1c7 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx @@ -1,8 +1,5 @@ -import { IndexingPolicy } from "@azure/cosmos"; -import { act } from "@testing-library/react"; import { AuthType } from "AuthType"; import { shallow } from "enzyme"; -import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView"; import ko from "knockout"; import React from "react"; import { updateCollection } from "../../../Common/dataAccess/updateCollection"; @@ -447,47 +444,3 @@ describe("SettingsComponent", () => { expect(settingsComponentInstance.isSaveSettingsButtonEnabled()).toBe(true); }); }); - -describe("SettingsComponent - indexing policy subscription", () => { - const baseProps: SettingsComponentProps = { - settingsTab: new CollectionSettingsTabV2({ - collection: collection, - tabKind: ViewModels.CollectionTabKind.CollectionSettingsV2, - title: "Scale & Settings", - tabPath: "", - node: undefined, - }), - }; - - it("subscribes to the correct container's indexing policy and updates state on change", async () => { - const containerId = collection.id(); - const mockIndexingPolicy: IndexingPolicy = { - automatic: false, - indexingMode: "lazy", - includedPaths: [{ path: "/foo/*" }], - excludedPaths: [{ path: "/bar/*" }], - compositeIndexes: [], - spatialIndexes: [], - vectorIndexes: [], - fullTextIndexes: [], - }; - - const wrapper = shallow(); - const instance = wrapper.instance() as SettingsComponent; - - await act(async () => { - useIndexingPolicyStore.setState({ - indexingPolicies: { - [containerId]: mockIndexingPolicy, - }, - }); - }); - - wrapper.update(); - - expect(wrapper.state("indexingPolicyContent")).toEqual(mockIndexingPolicy); - expect(wrapper.state("indexingPolicyContentBaseline")).toEqual(mockIndexingPolicy); - // @ts-expect-error: rawDataModel is intentionally accessed for test validation - expect(instance.collection.rawDataModel.indexingPolicy).toEqual(mockIndexingPolicy); - }); -}); diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 861980629..c3c7be1cb 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -13,7 +13,6 @@ import { ThroughputBucketsComponent, ThroughputBucketsComponentProps, } from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent"; -import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView"; import { useDatabases } from "Explorer/useDatabases"; import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { isCapabilityEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils"; @@ -74,6 +73,7 @@ import { parseConflictResolutionMode, parseConflictResolutionProcedure, } from "./SettingsUtils"; + interface SettingsV2TabInfo { tab: SettingsV2TabTypes; content: JSX.Element; @@ -182,7 +182,7 @@ export class SettingsComponent extends React.Component void; + constructor(props: SettingsComponentProps) { super(props); @@ -318,19 +318,8 @@ export class SettingsComponent extends React.Component { - this.refreshCollectionData(); - }, - (state) => state.indexingPolicies[this.collection.id()], - ); - this.refreshCollectionData(); - } - componentWillUnmount(): void { - if (this.unsubscribe) { - this.unsubscribe(); - } } + componentDidUpdate(): void { if (this.props.settingsTab.isActive()) { useCommandBar.getState().setContextButtons(this.getTabsButtons()); @@ -860,6 +849,7 @@ export class SettingsComponent extends React.Component => { - const containerId = this.collection.id(); - const latestIndexingPolicy = useIndexingPolicyStore.getState().indexingPolicies[containerId]; - const rawPolicy = latestIndexingPolicy ?? this.collection.indexingPolicy(); - - const latestCollection: DataModels.IndexingPolicy = { - automatic: rawPolicy?.automatic ?? true, - indexingMode: rawPolicy?.indexingMode ?? "consistent", - includedPaths: rawPolicy?.includedPaths ?? [], - excludedPaths: rawPolicy?.excludedPaths ?? [], - compositeIndexes: rawPolicy?.compositeIndexes ?? [], - spatialIndexes: rawPolicy?.spatialIndexes ?? [], - vectorIndexes: rawPolicy?.vectorIndexes ?? [], - fullTextIndexes: rawPolicy?.fullTextIndexes ?? [], - }; - - this.collection.rawDataModel.indexingPolicy = latestCollection; - this.setState({ - indexingPolicyContent: latestCollection, - indexingPolicyContentBaseline: latestCollection, - }); - }; private saveCollectionSettings = async (startKey: number): Promise => { const newCollection: DataModels.Collection = { ...this.collection.rawDataModel }; + if ( this.state.isSubSettingsSaveable || this.state.isContainerPolicyDirty || @@ -1283,6 +1252,7 @@ export class SettingsComponent extends React.Component diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx index 509373b8d..d601e3857 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx @@ -1,10 +1,10 @@ -import { MessageBar, MessageBarType } from "@fluentui/react"; import * as React from "react"; -import { handleError } from "../../../../../Common/ErrorHandlingUtils"; +import { MessageBar, MessageBarType } from "@fluentui/react"; import { mongoIndexTransformationRefreshingMessage, renderMongoIndexTransformationRefreshMessage, } from "../../SettingsRenderUtils"; +import { handleError } from "../../../../../Common/ErrorHandlingUtils"; import { isIndexTransforming } from "../../SettingsUtils"; export interface IndexingPolicyRefreshComponentProps { diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 0bd3fd7f1..c2d448634 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -73,16 +73,6 @@ exports[`SettingsComponent renders 1`] = ` "partitionKey", ], "rawDataModel": { - "indexingPolicy": { - "automatic": true, - "compositeIndexes": [], - "excludedPaths": [], - "fullTextIndexes": [], - "includedPaths": [], - "indexingMode": "consistent", - "spatialIndexes": [], - "vectorIndexes": [], - }, "uniqueKeyPolicy": { "uniqueKeys": [ { @@ -176,16 +166,6 @@ exports[`SettingsComponent renders 1`] = ` "partitionKey", ], "rawDataModel": { - "indexingPolicy": { - "automatic": true, - "compositeIndexes": [], - "excludedPaths": [], - "fullTextIndexes": [], - "includedPaths": [], - "indexingMode": "consistent", - "spatialIndexes": [], - "vectorIndexes": [], - }, "uniqueKeyPolicy": { "uniqueKeys": [ { @@ -260,25 +240,17 @@ exports[`SettingsComponent renders 1`] = ` indexingPolicyContent={ { "automatic": true, - "compositeIndexes": [], "excludedPaths": [], - "fullTextIndexes": [], "includedPaths": [], "indexingMode": "consistent", - "spatialIndexes": [], - "vectorIndexes": [], } } indexingPolicyContentBaseline={ { "automatic": true, - "compositeIndexes": [], "excludedPaths": [], - "fullTextIndexes": [], "includedPaths": [], "indexingMode": "consistent", - "spatialIndexes": [], - "vectorIndexes": [], } } isVectorSearchEnabled={false} @@ -352,16 +324,6 @@ exports[`SettingsComponent renders 1`] = ` "partitionKey", ], "rawDataModel": { - "indexingPolicy": { - "automatic": true, - "compositeIndexes": [], - "excludedPaths": [], - "fullTextIndexes": [], - "includedPaths": [], - "indexingMode": "consistent", - "spatialIndexes": [], - "vectorIndexes": [], - }, "uniqueKeyPolicy": { "uniqueKeys": [ { @@ -503,16 +465,6 @@ exports[`SettingsComponent renders 1`] = ` "partitionKey", ], "rawDataModel": { - "indexingPolicy": { - "automatic": true, - "compositeIndexes": [], - "excludedPaths": [], - "fullTextIndexes": [], - "includedPaths": [], - "indexingMode": "consistent", - "spatialIndexes": [], - "vectorIndexes": [], - }, "uniqueKeyPolicy": { "uniqueKeys": [ { diff --git a/src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx b/src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx deleted file mode 100644 index 7e3dfafbc..000000000 --- a/src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { CircleFilled } from "@fluentui/react-icons"; -import type { IIndexMetric } from "Explorer/Tabs/QueryTab/ResultsView"; -import { useIndexAdvisorStyles } from "Explorer/Tabs/QueryTab/StylesAdvisor"; -import * as React from "react"; - -// SDK response format -export interface IndexMetricsResponse { - UtilizedIndexes?: { - SingleIndexes?: Array<{ IndexSpec: string; IndexImpactScore?: string }>; - CompositeIndexes?: Array<{ IndexSpecs: string[]; IndexImpactScore?: string }>; - }; - PotentialIndexes?: { - SingleIndexes?: Array<{ IndexSpec: string; IndexImpactScore?: string }>; - CompositeIndexes?: Array<{ IndexSpecs: string[]; IndexImpactScore?: string }>; - }; -} - -export function parseIndexMetrics(indexMetrics: IndexMetricsResponse): { - included: IIndexMetric[]; - notIncluded: IIndexMetric[]; -} { - const included: IIndexMetric[] = []; - const notIncluded: IIndexMetric[] = []; - - // Process UtilizedIndexes (Included in Current Policy) - if (indexMetrics.UtilizedIndexes) { - // Single indexes - indexMetrics.UtilizedIndexes.SingleIndexes?.forEach((index) => { - included.push({ - index: index.IndexSpec, - impact: index.IndexImpactScore || "Utilized", - section: "Included", - path: index.IndexSpec, - }); - }); - - // Composite indexes - indexMetrics.UtilizedIndexes.CompositeIndexes?.forEach((index) => { - const compositeSpec = index.IndexSpecs.join(", "); - included.push({ - index: compositeSpec, - impact: index.IndexImpactScore || "Utilized", - section: "Included", - composite: index.IndexSpecs.map((spec) => { - const [path, order] = spec.trim().split(/\s+/); - return { - path: path.trim(), - order: order?.toLowerCase() === "desc" ? "descending" : "ascending", - }; - }), - }); - }); - } - - // Process PotentialIndexes (Not Included in Current Policy) - if (indexMetrics.PotentialIndexes) { - // Single indexes - indexMetrics.PotentialIndexes.SingleIndexes?.forEach((index) => { - notIncluded.push({ - index: index.IndexSpec, - impact: index.IndexImpactScore || "Unknown", - section: "Not Included", - path: index.IndexSpec, - }); - }); - - // Composite indexes - indexMetrics.PotentialIndexes.CompositeIndexes?.forEach((index) => { - const compositeSpec = index.IndexSpecs.join(", "); - notIncluded.push({ - index: compositeSpec, - impact: index.IndexImpactScore || "Unknown", - section: "Not Included", - composite: index.IndexSpecs.map((spec) => { - const [path, order] = spec.trim().split(/\s+/); - return { - path: path.trim(), - order: order?.toLowerCase() === "desc" ? "descending" : "ascending", - }; - }), - }); - }); - } - - return { included, notIncluded }; -} - -export const renderImpactDots = (impact: string): JSX.Element => { - const style = useIndexAdvisorStyles(); - let count = 0; - - if (impact === "High") { - count = 3; - } else if (impact === "Medium") { - count = 2; - } else if (impact === "Low") { - count = 1; - } - - return ( -
- {Array.from({ length: count }).map((_, i) => ( - - ))} -
- ); -}; diff --git a/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx b/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx index 9c9200aab..90a8a5672 100644 --- a/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx @@ -3,21 +3,18 @@ import QueryError from "Common/QueryError"; import { IndeterminateProgressBar } from "Explorer/Controls/IndeterminateProgressBar"; import { MessageBanner } from "Explorer/Controls/MessageBanner"; import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles"; -import useZoomLevel from "hooks/useZoomLevel"; import React from "react"; -import { conditionalClass } from "Utils/StyleUtils"; import RunQuery from "../../../../images/RunQuery.png"; import { QueryResults } from "../../../Contracts/ViewModels"; import { ErrorList } from "./ErrorList"; import { ResultsView } from "./ResultsView"; +import useZoomLevel from "hooks/useZoomLevel"; +import { conditionalClass } from "Utils/StyleUtils"; export interface ResultsViewProps { isMongoDB: boolean; queryResults: QueryResults; executeQueryDocumentsPage: (firstItemIndex: number) => Promise; - queryEditorContent?: string; - databaseId?: string; - containerId?: string; } interface QueryResultProps extends ResultsViewProps { @@ -52,8 +49,6 @@ export const QueryResultSection: React.FC = ({ queryResults, executeQueryDocumentsPage, isExecuting, - databaseId, - containerId, }: QueryResultProps): JSX.Element => { const styles = useQueryTabStyles(); const maybeSubQuery = queryEditorContent && /.*\(.*SELECT.*\)/i.test(queryEditorContent); @@ -96,9 +91,6 @@ export const QueryResultSection: React.FC = ({ queryResults={queryResults} executeQueryDocumentsPage={executeQueryDocumentsPage} isMongoDB={isMongoDB} - queryEditorContent={queryEditorContent} - databaseId={databaseId} - containerId={containerId} /> ) : ( diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.test.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.test.tsx index bf3ed538f..bc2e2f213 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.test.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.test.tsx @@ -52,9 +52,8 @@ describe("QueryTabComponent", () => { copilotVersion: "v3.0", }, }); - const propsMock: Readonly = { - collection: { databaseId: "CopilotSampleDB", id: () => "CopilotContainer" }, + collection: { databaseId: "CopilotSampleDB" }, onTabAccessor: () => jest.fn(), isExecutionError: false, tabId: "mockTabId", diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index 4dc60d916..06a0ddbf3 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -27,7 +27,6 @@ import { TabsState, useTabs } from "hooks/useTabs"; import React, { Fragment, createRef } from "react"; import "react-splitter-layout/lib/index.css"; import { format } from "react-string-format"; -import create from "zustand"; //TODO: Uncomment next two lines when query copilot is reinstated in DE // import QueryCommandIcon from "../../../../images/CopilotCommand.svg"; // import LaunchCopilot from "../../../../images/CopilotTabIcon.svg"; @@ -57,20 +56,6 @@ import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane"; import TabsBase from "../TabsBase"; import "./QueryTabComponent.less"; -export interface QueryMetadataStore { - userQuery: string; - databaseId: string; - containerId: string; - setMetadata: (query1: string, db: string, container: string) => void; -} - -export const useQueryMetadataStore = create((set) => ({ - userQuery: "", - databaseId: "", - containerId: "", - setMetadata: (query1, db, container) => set({ userQuery: query1, databaseId: db, containerId: container }), -})); - enum ToggleState { Result, QueryMetrics, @@ -275,10 +260,6 @@ class QueryTabComponentImpl extends React.Component => { - const query1 = this.state.sqlQueryEditorContent; - const db = this.props.collection.databaseId; - const container = this.props.collection.id(); - useQueryMetadataStore.getState().setMetadata(query1, db, container); this._iterator = undefined; setTimeout(async () => { @@ -794,8 +775,6 @@ class QueryTabComponentImpl extends React.Component QueryDocumentsPerPage( firstItemIndex, @@ -811,8 +790,6 @@ class QueryTabComponentImpl extends React.Component this._executeQueryDocumentsPage(firstItemIndex) } diff --git a/src/Explorer/Tabs/QueryTab/ResultsView.test.tsx b/src/Explorer/Tabs/QueryTab/ResultsView.test.tsx deleted file mode 100644 index 31bdf939c..000000000 --- a/src/Explorer/Tabs/QueryTab/ResultsView.test.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import "@testing-library/jest-dom"; -import { render, screen, waitFor } from "@testing-library/react"; -import { IndexAdvisorTab } from "Explorer/Tabs/QueryTab/ResultsView"; -import React from "react"; - -const mockReplace = jest.fn(); -const mockFetchAll = jest.fn(); -const mockRead = jest.fn(); -const mockLogConsoleProgress = jest.fn(); -const mockHandleError = jest.fn(); - -const indexMetricsResponse = { - UtilizedIndexes: { - SingleIndexes: [{ IndexSpec: "/foo/?", IndexImpactScore: "High" }], - CompositeIndexes: [{ IndexSpecs: ["/baz/? DESC", "/qux/? ASC"], IndexImpactScore: "Low" }], - }, - PotentialIndexes: { - SingleIndexes: [{ IndexSpec: "/bar/?", IndexImpactScore: "Medium" }], - CompositeIndexes: [] as Array<{ IndexSpecs: string[]; IndexImpactScore?: string }>, - }, -}; - -const mockQueryResults = { - documents: [] as unknown[], - hasMoreResults: false, - itemCount: 0, - firstItemIndex: 0, - lastItemIndex: 0, - requestCharge: 0, - activityId: "test-activity-id", -}; - -mockRead.mockResolvedValue({ - resource: { - indexingPolicy: { - automatic: true, - indexingMode: "consistent", - includedPaths: [{ path: "/*" }, { path: "/foo/?" }], - excludedPaths: [], - }, - partitionKey: "pk", - }, -}); - -mockReplace.mockResolvedValue({ - resource: { - indexingPolicy: { - automatic: true, - indexingMode: "consistent", - includedPaths: [{ path: "/*" }], - excludedPaths: [], - }, - }, -}); - -jest.mock("Common/CosmosClient", () => ({ - client: () => ({ - database: () => ({ - container: () => ({ - items: { - query: () => ({ - fetchAll: mockFetchAll, - }), - }, - read: mockRead, - replace: mockReplace, - }), - }), - }), -})); - -jest.mock("./StylesAdvisor", () => ({ - useIndexAdvisorStyles: () => ({}), -})); - -jest.mock("../../../Utils/NotificationConsoleUtils", () => ({ - logConsoleProgress: (...args: unknown[]) => { - mockLogConsoleProgress(...args); - return () => {}; - }, -})); - -jest.mock("../../../Common/ErrorHandlingUtils", () => ({ - handleError: (...args: unknown[]) => mockHandleError(...args), -})); - -beforeEach(() => { - jest.clearAllMocks(); - mockFetchAll.mockResolvedValue({ indexMetrics: indexMetricsResponse }); -}); - -describe("IndexAdvisorTab Basic Tests", () => { - test("component renders without crashing", () => { - const { container } = render( - , - ); - expect(container).toBeTruthy(); - }); - - test("renders component and handles missing parameters", () => { - const { container } = render(); - expect(container).toBeTruthy(); - // Should not crash when parameters are missing - }); - - test("fetches index metrics with query results", async () => { - render( - , - ); - await waitFor(() => expect(mockFetchAll).toHaveBeenCalled()); - }); - - test("displays content after loading", async () => { - render( - , - ); - // Wait for the component to finish loading - await waitFor(() => expect(mockFetchAll).toHaveBeenCalled()); - // Component should have rendered some content - expect(screen.getByText(/Index Advisor/i)).toBeInTheDocument(); - }); - - test("calls log console progress when fetching metrics", async () => { - render( - , - ); - await waitFor(() => expect(mockLogConsoleProgress).toHaveBeenCalled()); - }); - - test("handles error when fetch fails", async () => { - mockFetchAll.mockRejectedValueOnce(new Error("fetch failed")); - render( - , - ); - await waitFor(() => expect(mockHandleError).toHaveBeenCalled(), { timeout: 3000 }); - }); - - test("renders with all required props", () => { - const { container } = render( - , - ); - expect(container).toBeTruthy(); - expect(container.firstChild).toBeTruthy(); - }); -}); diff --git a/src/Explorer/Tabs/QueryTab/ResultsView.tsx b/src/Explorer/Tabs/QueryTab/ResultsView.tsx index fbc56e89f..64b987b69 100644 --- a/src/Explorer/Tabs/QueryTab/ResultsView.tsx +++ b/src/Explorer/Tabs/QueryTab/ResultsView.tsx @@ -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 = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => { const styles = useQueryTabStyles(); /* eslint-disable react/prop-types */ @@ -543,331 +523,14 @@ const QueryStatsTab: React.FC> = ({ 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(null); - const [showIncluded, setShowIncluded] = useState(true); - const [showNotIncluded, setShowNotIncluded] = useState(true); - const [selectedIndexes, setSelectedIndexes] = useState([]); - const [selectAll, setSelectAll] = useState(false); - const [updateMessageShown, setUpdateMessageShown] = useState(false); - const [included, setIncludedIndexes] = useState([]); - const [notIncluded, setNotIncludedIndexes] = useState([]); - 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 ( - - -
- {isNotIncluded ? ( - selected.index === item.index)} - onChange={(_, data) => handleCheckboxChange(item, data.checked === true)} - /> - ) : isHeader && item.index === "Not Included in Current Policy" && notIncluded.length > 0 ? ( - handleSelectAll(data.checked === true)} /> - ) : ( -
- )} - {isHeader ? ( - { - 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 ? ( - - ) : ( - - ) - ) : showNotIncluded ? ( - - ) : ( - - )} - - ) : ( -
- )} -
{item.index}
-
- {!isHeader && item.impact} -
-
{!isHeader && renderImpactDots(item.impact)}
-
-
-
- ); - }; - 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 ( -
- -
- ); - } - - return ( -
-
- {updateMessageShown ? ( - <> - - - - - Your indexing policy has been updated with the new included paths. You may review the changes in Scale & - Settings. - - - ) : ( - <> - - 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.{" "} - - Learn more about Indexing Metrics - - .{" "} - - - )} -
-
Indexes analysis
- - - - -
-
-
-
Index
-
- Estimated Impact -
-
-
-
-
- {indexMetricItems.map(renderRow)} -
- {selectedIndexes.length > 0 && ( -
- {isUpdating ? ( -
- {" "} -
- ) : ( - - )} -
- )} -
- ); -}; -export const ResultsView: React.FC = ({ - isMongoDB, - queryResults, - executeQueryDocumentsPage, - queryEditorContent, - databaseId, - containerId, -}) => { +export const ResultsView: React.FC = ({ isMongoDB, queryResults, executeQueryDocumentsPage }) => { const styles = useQueryTabStyles(); const [activeTab, setActiveTab] = useState(ResultsTabs.Results); const onTabSelect = useCallback((event: SelectTabEvent, data: SelectTabData) => { setActiveTab(data.value as ResultsTabs); }, []); + return (
@@ -885,13 +548,6 @@ export const ResultsView: React.FC = ({ > Query Stats - - Index Advisor -
{activeTab === ResultsTabs.Results && ( @@ -902,30 +558,7 @@ export const ResultsView: React.FC = ({ /> )} {activeTab === ResultsTabs.QueryStats && } - {activeTab === ResultsTabs.IndexAdvisor && ( - - )}
); }; -export interface IndexingPolicyStore { - indexingPolicies: { [containerId: string]: IndexingPolicy }; - setIndexingPolicyFor: (containerId: string, indexingPolicy: IndexingPolicy) => void; -} - -export const useIndexingPolicyStore = create((set) => ({ - indexingPolicies: {}, - setIndexingPolicyFor: (containerId, indexingPolicy) => - set((state) => ({ - indexingPolicies: { - ...state.indexingPolicies, - [containerId]: { ...indexingPolicy }, - }, - })), -})); diff --git a/src/Explorer/Tabs/QueryTab/StylesAdvisor.ts b/src/Explorer/Tabs/QueryTab/StylesAdvisor.ts deleted file mode 100644 index 29f62b35a..000000000 --- a/src/Explorer/Tabs/QueryTab/StylesAdvisor.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { makeStyles } from "@fluentui/react-components"; -export type IndexAdvisorStyles = ReturnType; -export const useIndexAdvisorStyles = makeStyles({ - indexAdvisorMessage: { - padding: "1rem", - fontSize: "1.2rem", - display: "flex", - alignItems: "center", - gap: "0.5rem", - }, - indexAdvisorSuccessIcon: { - width: "18px", - height: "18px", - borderRadius: "50%", - backgroundColor: "#107C10", - display: "flex", - alignItems: "center", - justifyContent: "center", - }, - indexAdvisorTitle: { - padding: "1rem", - fontSize: "1.3rem", - fontWeight: "bold", - }, - indexAdvisorTable: { - display: "block", - alignItems: "center", - marginBottom: "7rem", - }, - indexAdvisorGrid: { - display: "grid", - gridTemplateColumns: "30px 30px 1fr 50px 120px", - alignItems: "center", - gap: "15px", - fontWeight: "bold", - }, - indexAdvisorCheckboxSpacer: { - width: "18px", - height: "18px", - }, - indexAdvisorChevronSpacer: { - width: "24px", - }, - indexAdvisorRowBold: { - fontWeight: "bold", - }, - indexAdvisorRowNormal: { - fontWeight: "normal", - }, - indexAdvisorRowImpactHeader: { - fontSize: 0, - }, - indexAdvisorRowImpact: { - fontWeight: "normal", - }, - indexAdvisorImpactDot: { - color: "#0078D4", - fontSize: "12px", - display: "inline-flex", - }, - indexAdvisorImpactDots: { - display: "flex", - alignItems: "center", - gap: "4px", - }, - indexAdvisorButtonBar: { - padding: "1rem", - marginTop: "-7rem", - flexWrap: "wrap", - }, - indexAdvisorButtonSpinner: { - marginTop: "1rem", - minWidth: "320px", - minHeight: "40px", - display: "flex", - alignItems: "left", - justifyContent: "left", - marginLeft: "10rem", - }, - indexAdvisorButton: { - backgroundColor: "#0078D4", - color: "white", - padding: "8px 16px", - border: "none", - borderRadius: "4px", - cursor: "pointer", - marginTop: "1rem", - fontSize: "1rem", - fontWeight: 500, - transition: "background 0.2s", - ":hover": { - backgroundColor: "#005a9e", - }, - }, -}); diff --git a/src/Explorer/Tabs/QueryTab/useQueryMetadataStore.ts b/src/Explorer/Tabs/QueryTab/useQueryMetadataStore.ts deleted file mode 100644 index cccf3c7bb..000000000 --- a/src/Explorer/Tabs/QueryTab/useQueryMetadataStore.ts +++ /dev/null @@ -1,15 +0,0 @@ -import create from "zustand"; - -interface QueryMetadataStore { - userQuery: string; - databaseId: string; - containerId: string; - setMetadata: (query1: string, db: string, container: string) => void; -} - -export const useQueryMetadataStore = create((set) => ({ - userQuery: "", - databaseId: "", - containerId: "", - setMetadata: (query1, db, container) => set({ userQuery: query1, databaseId: db, containerId: container }), -}));