From 312bcb8e047e77bd8eda281a4fc586c86a0afc97 Mon Sep 17 00:00:00 2001 From: nishthaAhujaa Date: Thu, 16 Oct 2025 00:25:37 +0530 Subject: [PATCH] overwrite fixes --- .../Tabs/QueryTab/IndexAdvisorUtils.tsx | 137 +++++++++--------- src/Explorer/Tabs/QueryTab/ResultsView.tsx | 77 ++++++---- 2 files changed, 112 insertions(+), 102 deletions(-) diff --git a/src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx b/src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx index 168aa7e43..7e3dfafbc 100644 --- a/src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx +++ b/src/Explorer/Tabs/QueryTab/IndexAdvisorUtils.tsx @@ -2,91 +2,86 @@ 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"; -interface IndexObject { - index: string; - impact: string; - section: "Included" | "Not Included" | "Header"; - composite?: { - path: string; - order: "ascending" | "descending"; - }[]; - path?: string; + +// 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 interface IndexMetricsJson { - included?: IIndexMetric[]; - notIncluded?: IIndexMetric[]; -} -export function parseIndexMetrics(indexMetrics: string | IndexMetricsJson): { +export function parseIndexMetrics(indexMetrics: IndexMetricsResponse): { included: IIndexMetric[]; notIncluded: IIndexMetric[]; } { - // If already JSON, just extract arrays - if (typeof indexMetrics === "object" && indexMetrics !== null) { - return { - included: Array.isArray(indexMetrics.included) ? indexMetrics.included : [], - notIncluded: Array.isArray(indexMetrics.notIncluded) ? indexMetrics.notIncluded : [], - }; - } - - // Otherwise, parse as string (current SDK) const included: IIndexMetric[] = []; const notIncluded: IIndexMetric[] = []; - const lines = (indexMetrics as string) - .split("\n") - .map((line) => line.trim()) - .filter(Boolean); - let currentSection = ""; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.startsWith("Utilized Single Indexes") || line.startsWith("Utilized Composite Indexes")) { - currentSection = "included"; - } else if (line.startsWith("Potential Single Indexes") || line.startsWith("Potential Composite Indexes")) { - currentSection = "notIncluded"; - } else if (line.startsWith("Index Spec:")) { - const index = line.replace("Index Spec:", "").trim(); - const impactLine = lines[i + 1]; - const impact = impactLine?.includes("Index Impact Score:") ? impactLine.split(":")[1].trim() : "Unknown"; - const isComposite = index.includes(","); + // 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, + }); + }); - const sectionMap: Record = { - included: "Included", - notIncluded: "Not Included", - }; - - const indexObj: IndexObject = { index, impact, section: sectionMap[currentSection] ?? "Header" }; - if (isComposite) { - indexObj.composite = index.split(",").map((part: string) => { - const [path, order] = part.trim().split(/\s+/); + // 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", }; - }); - } else { - let path = "/unknown/*"; - const pathRegex = /\/[^/\s*?]+(?:\/[^/\s*?]+)*(\/\*|\?)/; - const match = index.match(pathRegex); - if (match) { - path = match[0]; - } else { - const simplePathRegex = /\/[^/\s]+/; - const simpleMatch = index.match(simplePathRegex); - if (simpleMatch) { - path = simpleMatch[0] + "/*"; - } - } - indexObj.path = path; - } - - if (currentSection === "included") { - included.push(indexObj); - } else if (currentSection === "notIncluded") { - notIncluded.push(indexObj); - } - } + }), + }); + }); } + + // 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 }; } diff --git a/src/Explorer/Tabs/QueryTab/ResultsView.tsx b/src/Explorer/Tabs/QueryTab/ResultsView.tsx index ec51f989e..035790bdf 100644 --- a/src/Explorer/Tabs/QueryTab/ResultsView.tsx +++ b/src/Explorer/Tabs/QueryTab/ResultsView.tsx @@ -27,8 +27,9 @@ import copy from "clipboard-copy"; 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 } from "Explorer/Tabs/QueryTab/IndexAdvisorUtils"; +import { parseIndexMetrics, renderImpactDots, type IndexMetricsResponse } from "Explorer/Tabs/QueryTab/IndexAdvisorUtils"; import { IDocument, useQueryMetadataStore } from "Explorer/Tabs/QueryTab/QueryTabComponent"; import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles"; import React, { useCallback, useEffect, useState } from "react"; @@ -394,9 +395,8 @@ const QueryStatsTab: React.FC> = ({ query }, { metric: "User defined function execution time", - value: `${ - aggregatedQueryMetrics.runtimeExecutionTimes?.userDefinedFunctionExecutionTime?.toString() || 0 - } ms`, + value: `${aggregatedQueryMetrics.runtimeExecutionTimes?.userDefinedFunctionExecutionTime?.toString() || 0 + } ms`, toolTip: "Total time spent executing user-defined functions", }, { @@ -544,11 +544,11 @@ export interface IIndexMetric { path?: string; composite?: { path: string; order: string }[]; } -export const IndexAdvisorTab: React.FC = () => { +export const IndexAdvisorTab: React.FC<{ queryResults?: QueryResults }> = ({ queryResults }) => { const style = useIndexAdvisorStyles(); const { userQuery, databaseId, containerId } = useQueryMetadataStore(); - const [loading, setLoading] = useState(true); - const [indexMetrics, setIndexMetrics] = useState(null); + const [loading, setLoading] = useState(false); + const [indexMetrics, setIndexMetrics] = useState(null); const [showIncluded, setShowIncluded] = useState(true); const [showNotIncluded, setShowNotIncluded] = useState(true); const [selectedIndexes, setSelectedIndexes] = useState([]); @@ -560,32 +560,47 @@ export const IndexAdvisorTab: React.FC = () => { const [justUpdatedPolicy, setJustUpdatedPolicy] = useState(false); const indexingMetricsDocLink = "https://learn.microsoft.com/azure/cosmos-db/nosql/index-metrics"; + const fetchIndexMetrics = async () => { + if (!userQuery || !databaseId || !containerId) { + return; + } + + setLoading(true); + const clearMessage = logConsoleProgress(`Querying items with IndexMetrics in container ${containerId}`); + try { + const containerRef = client().database(databaseId).container(containerId); + const { resource: containerDef } = await containerRef.read(); + + const querySpec = { + query: userQuery, + }; + const sdkResponse = await client() + .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(() => { - const fetchIndexMetrics = async () => { - const clearMessage = logConsoleProgress(`Querying items with IndexMetrics in container ${containerId}`); - try { - const querySpec = { - query: userQuery, - }; - const sdkResponse = await client() - .database(databaseId) - .container(containerId) - .items.query(querySpec, { - populateIndexMetrics: true, - }) - .fetchAll(); - setIndexMetrics(sdkResponse.indexMetrics); - } catch (error) { - handleError(error, "queryItemsWithIndexMetrics", `Error querying items from ${containerId}`); - } finally { - clearMessage(); - setLoading(false); - } - }; - if (userQuery && databaseId && containerId) { + if (userQuery && databaseId && containerId && queryResults) { fetchIndexMetrics(); } - }, [userQuery, databaseId, containerId]); + }, [queryResults]); useEffect(() => { if (!indexMetrics) { @@ -869,7 +884,7 @@ export const ResultsView: React.FC = ({ isMongoDB, queryResult /> )} {activeTab === ResultsTabs.QueryStats && } - {activeTab === ResultsTabs.IndexAdvisor && } + {activeTab === ResultsTabs.IndexAdvisor && } );