fix:auto format

This commit is contained in:
Archie Agarwal 2025-07-07 15:41:10 +05:30
parent f2044f2054
commit 3949a0ecce
7 changed files with 290 additions and 282 deletions

View File

@ -303,15 +303,18 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if (this.props.settingsTab.isActive()) { if (this.props.settingsTab.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons()); useCommandBar.getState().setContextButtons(this.getTabsButtons());
} }
this.unsubscribe = useIndexingPolicyStore.subscribe(() => { this.unsubscribe = useIndexingPolicyStore.subscribe(
this.refreshCollectionData(); () => {
}, this.refreshCollectionData();
(state) => state.indexingPolicies[this.collection.id()] },
(state) => state.indexingPolicies[this.collection.id()],
); );
this.refreshCollectionData(); this.refreshCollectionData();
} }
componentWillUnmount(): void { componentWillUnmount(): void {
if (this.unsubscribe) { this.unsubscribe(); } if (this.unsubscribe) {
this.unsubscribe();
}
} }
componentDidUpdate(): void { componentDidUpdate(): void {
if (this.props.settingsTab.isActive()) { if (this.props.settingsTab.isActive()) {
@ -859,8 +862,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions; const throughputDelta = (newThroughput - this.offer.autoscaleMaxThroughput) * numberOfRegions;
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) { if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${this.totalThroughputUsed + throughputDelta throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
} RU/s. Change total throughput limit in cost management.`; this.totalThroughputUsed + throughputDelta
} RU/s. Change total throughput limit in cost management.`;
} }
this.setState({ autoPilotThroughput: newThroughput, throughputError }); this.setState({ autoPilotThroughput: newThroughput, throughputError });
}; };
@ -871,8 +875,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions; const throughputDelta = (newThroughput - this.offer.manualThroughput) * numberOfRegions;
if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) { if (throughputCap && throughputCap !== -1 && throughputCap - this.totalThroughputUsed < throughputDelta) {
throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${this.totalThroughputUsed + throughputDelta throughputError = `Your account is currently configured with a total throughput limit of ${throughputCap} RU/s. This update isn't possible because it would increase the total throughput to ${
} RU/s. Change total throughput limit in cost management.`; this.totalThroughputUsed + throughputDelta
} RU/s. Change total throughput limit in cost management.`;
} }
this.setState({ throughput: newThroughput, throughputError }); this.setState({ throughput: newThroughput, throughputError });
}; };
@ -992,8 +997,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
newCollection.changeFeedPolicy = newCollection.changeFeedPolicy =
this.changeFeedPolicyVisible && this.state.changeFeedPolicy === ChangeFeedPolicyState.On this.changeFeedPolicyVisible && this.state.changeFeedPolicy === ChangeFeedPolicyState.On
? { ? {
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration, retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration,
} }
: undefined; : undefined;
newCollection.analyticalStorageTtl = this.getAnalyticalStorageTtl(); newCollection.analyticalStorageTtl = this.getAnalyticalStorageTtl();

View File

@ -1,85 +1,88 @@
import type { IIndexMetric } from "Explorer/Tabs/QueryTab/ResultsView"; import type { IIndexMetric } from "Explorer/Tabs/QueryTab/ResultsView";
interface IndexObject { interface IndexObject {
index: string; index: string;
impact: string; impact: string;
section: "Included" | "Not Included" | "Header"; section: "Included" | "Not Included" | "Header";
composite?: { composite?: {
path: string; path: string;
order: "ascending" | "descending"; order: "ascending" | "descending";
}[]; }[];
path?: string; path?: string;
} }
export interface IndexMetricsJson { export interface IndexMetricsJson {
included?: IIndexMetric[]; included?: IIndexMetric[];
notIncluded?: IIndexMetric[]; notIncluded?: IIndexMetric[];
} }
export function parseIndexMetrics(indexMetrics: string | IndexMetricsJson): { export function parseIndexMetrics(indexMetrics: string | IndexMetricsJson): {
included: IIndexMetric[]; included: IIndexMetric[];
notIncluded: IIndexMetric[]; notIncluded: IIndexMetric[];
} { } {
// If already JSON, just extract arrays // If already JSON, just extract arrays
if (typeof indexMetrics === "object" && indexMetrics !== null) { if (typeof indexMetrics === "object" && indexMetrics !== null) {
return { return {
included: Array.isArray(indexMetrics.included) ? indexMetrics.included : [], included: Array.isArray(indexMetrics.included) ? indexMetrics.included : [],
notIncluded: Array.isArray(indexMetrics.notIncluded) ? indexMetrics.notIncluded : [], notIncluded: Array.isArray(indexMetrics.notIncluded) ? indexMetrics.notIncluded : [],
}; };
} }
// Otherwise, parse as string (current SDK) // Otherwise, parse as string (current SDK)
const included: IIndexMetric[] = []; const included: IIndexMetric[] = [];
const notIncluded: IIndexMetric[] = []; const notIncluded: IIndexMetric[] = [];
const lines = (indexMetrics as string).split("\n").map((line) => line.trim()).filter(Boolean); const lines = (indexMetrics as string)
let currentSection = ""; .split("\n")
for (let i = 0; i < lines.length; i++) { .map((line) => line.trim())
const line = lines[i]; .filter(Boolean);
if (line.startsWith("Utilized Single Indexes") || line.startsWith("Utilized Composite Indexes")) { let currentSection = "";
currentSection = "included"; for (let i = 0; i < lines.length; i++) {
} else if (line.startsWith("Potential Single Indexes") || line.startsWith("Potential Composite Indexes")) { const line = lines[i];
currentSection = "notIncluded"; if (line.startsWith("Utilized Single Indexes") || line.startsWith("Utilized Composite Indexes")) {
} else if (line.startsWith("Index Spec:")) { currentSection = "included";
const index = line.replace("Index Spec:", "").trim(); } else if (line.startsWith("Potential Single Indexes") || line.startsWith("Potential Composite Indexes")) {
const impactLine = lines[i + 1]; currentSection = "notIncluded";
const impact = impactLine?.includes("Index Impact Score:") ? impactLine.split(":")[1].trim() : "Unknown"; } 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(","); const isComposite = index.includes(",");
const sectionMap: Record<string, "Included" | "Not Included"> = { const sectionMap: Record<string, "Included" | "Not Included"> = {
included: "Included", included: "Included",
notIncluded: "Not Included", notIncluded: "Not Included",
}; };
const indexObj: IndexObject = { index, impact, section: sectionMap[currentSection] ?? "Header", }; const indexObj: IndexObject = { index, impact, section: sectionMap[currentSection] ?? "Header" };
if (isComposite) { if (isComposite) {
indexObj.composite = index.split(",").map((part: string) => { indexObj.composite = index.split(",").map((part: string) => {
const [path, order] = part.trim().split(/\s+/); const [path, order] = part.trim().split(/\s+/);
return { return {
path: path.trim(), path: path.trim(),
order: order?.toLowerCase() === "desc" ? "descending" : "ascending", order: order?.toLowerCase() === "desc" ? "descending" : "ascending",
}; };
}); });
} else { } else {
let path = "/unknown/*"; let path = "/unknown/*";
const pathRegex = /\/[^/\s*?]+(?:\/[^/\s*?]+)*(\/\*|\?)/; const pathRegex = /\/[^/\s*?]+(?:\/[^/\s*?]+)*(\/\*|\?)/;
const match = index.match(pathRegex); const match = index.match(pathRegex);
if (match) { if (match) {
path = match[0]; path = match[0];
} else { } else {
const simplePathRegex = /\/[^/\s]+/; const simplePathRegex = /\/[^/\s]+/;
const simpleMatch = index.match(simplePathRegex); const simpleMatch = index.match(simplePathRegex);
if (simpleMatch) { if (simpleMatch) {
path = simpleMatch[0] + "/*"; path = simpleMatch[0] + "/*";
} }
}
indexObj.path = path;
}
if (currentSection === "included") {
included.push(indexObj);
} else if (currentSection === "notIncluded") {
notIncluded.push(indexObj);
}
} }
indexObj.path = path;
}
if (currentSection === "included") {
included.push(indexObj);
} else if (currentSection === "notIncluded") {
notIncluded.push(indexObj);
}
} }
return { included, notIncluded }; }
return { included, notIncluded };
} }

View File

@ -21,185 +21,182 @@ Index Spec: /baz/? DESC, /qux/? ASC
Index Impact Score: Low Index Impact Score: Low
`; `;
mockRead.mockResolvedValue({ mockRead.mockResolvedValue({
resource: { resource: {
indexingPolicy: { indexingPolicy: {
automatic: true, automatic: true,
indexingMode: "consistent", indexingMode: "consistent",
includedPaths: [{ path: "/*" }, { path: "/foo/?" }], includedPaths: [{ path: "/*" }, { path: "/foo/?" }],
excludedPaths: [], excludedPaths: [],
},
partitionKey: "pk",
}, },
partitionKey: "pk",
},
}); });
mockReplace.mockResolvedValue({ mockReplace.mockResolvedValue({
resource: { resource: {
indexingPolicy: { indexingPolicy: {
automatic: true, automatic: true,
indexingMode: "consistent", indexingMode: "consistent",
includedPaths: [{ path: "/*" }], includedPaths: [{ path: "/*" }],
excludedPaths: [], excludedPaths: [],
},
}, },
},
}); });
jest.mock("./QueryTabComponent", () => ({ jest.mock("./QueryTabComponent", () => ({
useQueryMetadataStore: () => ({ useQueryMetadataStore: () => ({
userQuery: "SELECT * FROM c", userQuery: "SELECT * FROM c",
databaseId: "db1", databaseId: "db1",
containerId: "col1", containerId: "col1",
}), }),
})); }));
jest.mock("Common/CosmosClient", () => ({ jest.mock("Common/CosmosClient", () => ({
client: () => ({ client: () => ({
database: () => ({ database: () => ({
container: () => ({ container: () => ({
items: { items: {
query: () => ({ query: () => ({
fetchAll: mockFetchAll.mockResolvedValueOnce({ indexMetrics: indexMetricsString }) fetchAll: mockFetchAll.mockResolvedValueOnce({ indexMetrics: indexMetricsString }),
, }),
}), },
}, read: mockRead,
read: mockRead, replace: mockReplace,
replace: mockReplace, }),
}),
}),
}), }),
}),
})); }));
jest.mock("./Indexadvisor", () => ({ jest.mock("./Indexadvisor", () => ({
useIndexAdvisorStyles: () => ({}), useIndexAdvisorStyles: () => ({}),
})); }));
jest.mock("../../../Utils/NotificationConsoleUtils", () => ({ jest.mock("../../../Utils/NotificationConsoleUtils", () => ({
logConsoleProgress: (...args: unknown[]) => { logConsoleProgress: (...args: unknown[]) => {
mockLogConsoleProgress(...args); mockLogConsoleProgress(...args);
return () => { }; return () => {};
}, },
})); }));
jest.mock("../../../Common/ErrorHandlingUtils", () => { jest.mock("../../../Common/ErrorHandlingUtils", () => {
return { return {
handleError: (...args: unknown[]) => mockHandleError(...args), handleError: (...args: unknown[]) => mockHandleError(...args),
}; };
}); });
test("logs progress message when fetching index metrics", async () => { test("logs progress message when fetching index metrics", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => await waitFor(() => expect(mockLogConsoleProgress).toHaveBeenCalledWith(expect.stringContaining("IndexMetrics")));
expect(mockLogConsoleProgress).toHaveBeenCalledWith(expect.stringContaining("IndexMetrics"))
);
}); });
test("renders both Included and Not Included sections after loading", async () => { test("renders both Included and Not Included sections after loading", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("Included in Current Policy")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("Included in Current Policy")).toBeInTheDocument());
expect(screen.getByText("Not Included in Current Policy")).toBeInTheDocument(); expect(screen.getByText("Not Included in Current Policy")).toBeInTheDocument();
expect(screen.getByText("/foo/?")).toBeInTheDocument(); expect(screen.getByText("/foo/?")).toBeInTheDocument();
expect(screen.getByText("/bar/?")).toBeInTheDocument(); expect(screen.getByText("/bar/?")).toBeInTheDocument();
}); });
test("shows update button only when an index is selected", async () => { test("shows update button only when an index is selected", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument());
const checkboxes = screen.getAllByRole("checkbox"); const checkboxes = screen.getAllByRole("checkbox");
expect(checkboxes.length).toBeGreaterThan(1); expect(checkboxes.length).toBeGreaterThan(1);
fireEvent.click(checkboxes[1]); fireEvent.click(checkboxes[1]);
expect(screen.getByText(/Update Indexing Policy/)).toBeInTheDocument(); expect(screen.getByText(/Update Indexing Policy/)).toBeInTheDocument();
fireEvent.click(checkboxes[1]); fireEvent.click(checkboxes[1]);
expect(screen.queryByText(/Update Indexing Policy/)).not.toBeInTheDocument(); expect(screen.queryByText(/Update Indexing Policy/)).not.toBeInTheDocument();
}); });
test("calls replace when update policy is confirmed", async () => { test("calls replace when update policy is confirmed", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument());
const checkboxes = screen.getAllByRole("checkbox"); const checkboxes = screen.getAllByRole("checkbox");
fireEvent.click(checkboxes[1]); fireEvent.click(checkboxes[1]);
const updateButton = screen.getByText(/Update Indexing Policy/); const updateButton = screen.getByText(/Update Indexing Policy/);
fireEvent.click(updateButton); fireEvent.click(updateButton);
await waitFor(() => expect(mockReplace).toHaveBeenCalled()); await waitFor(() => expect(mockReplace).toHaveBeenCalled());
}); });
test("calls replace when update button is clicked", async () => { test("calls replace when update button is clicked", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument());
const checkboxes = screen.getAllByRole("checkbox"); const checkboxes = screen.getAllByRole("checkbox");
fireEvent.click(checkboxes[1]); // Select /bar/? fireEvent.click(checkboxes[1]); // Select /bar/?
fireEvent.click(screen.getByText(/Update Indexing Policy/)); fireEvent.click(screen.getByText(/Update Indexing Policy/));
await waitFor(() => expect(mockReplace).toHaveBeenCalled()); await waitFor(() => expect(mockReplace).toHaveBeenCalled());
}); });
test("fetches indexing policy via read", async () => { test("fetches indexing policy via read", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => { await waitFor(() => {
expect(mockRead).toHaveBeenCalled(); expect(mockRead).toHaveBeenCalled();
}); });
}); });
test("selects all indexes when select-all is clicked", async () => { test("selects all indexes when select-all is clicked", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument());
const checkboxes = screen.getAllByRole("checkbox"); const checkboxes = screen.getAllByRole("checkbox");
fireEvent.click(checkboxes[0]); fireEvent.click(checkboxes[0]);
checkboxes.forEach((cb) => { checkboxes.forEach((cb) => {
expect(cb).toBeChecked(); expect(cb).toBeChecked();
}); });
}); });
test("shows spinner while loading and hides after fetchIndexMetrics resolves", async () => { test("shows spinner while loading and hides after fetchIndexMetrics resolves", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
expect(screen.getByRole("progressbar")).toBeInTheDocument(); expect(screen.getByRole("progressbar")).toBeInTheDocument();
await waitFor(() => expect(screen.queryByRole("progressbar")).not.toBeInTheDocument()); await waitFor(() => expect(screen.queryByRole("progressbar")).not.toBeInTheDocument());
}); });
test("calls fetchAll with correct query and options", async () => { test("calls fetchAll with correct query and options", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(mockFetchAll).toHaveBeenCalled()); await waitFor(() => expect(mockFetchAll).toHaveBeenCalled());
}); });
test("renders IndexAdvisorTab when clicked from ResultsView", async () => { test("renders IndexAdvisorTab when clicked from ResultsView", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("Included in Current Policy")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("Included in Current Policy")).toBeInTheDocument());
expect(screen.getByText("/foo/?")).toBeInTheDocument(); expect(screen.getByText("/foo/?")).toBeInTheDocument();
}); });
test("renders index metrics from SDK response", async () => { test("renders index metrics from SDK response", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("/foo/?")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("/foo/?")).toBeInTheDocument());
expect(screen.getByText("/bar/?")).toBeInTheDocument(); expect(screen.getByText("/bar/?")).toBeInTheDocument();
expect(screen.getByText("/baz/? DESC, /qux/? ASC")).toBeInTheDocument(); expect(screen.getByText("/baz/? DESC, /qux/? ASC")).toBeInTheDocument();
}); });
test("calls handleError if fetchIndexMetrics throws", async () => { test("calls handleError if fetchIndexMetrics throws", async () => {
mockFetchAll.mockRejectedValueOnce(new Error("fail")); mockFetchAll.mockRejectedValueOnce(new Error("fail"));
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(mockHandleError).toHaveBeenCalled()); await waitFor(() => expect(mockHandleError).toHaveBeenCalled());
}); });
test("calls handleError if fetchIndexMetrics throws2nd", async () => { test("calls handleError if fetchIndexMetrics throws2nd", async () => {
mockFetchAll.mockRejectedValueOnce(new Error("fail")); mockFetchAll.mockRejectedValueOnce(new Error("fail"));
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(mockHandleError).toHaveBeenCalled()); await waitFor(() => expect(mockHandleError).toHaveBeenCalled());
expect(screen.queryByRole("status")).not.toBeInTheDocument(); expect(screen.queryByRole("status")).not.toBeInTheDocument();
}); });
test("IndexingPolicyStore stores updated policy on componentDidMount", async () => { test("IndexingPolicyStore stores updated policy on componentDidMount", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(mockRead).toHaveBeenCalled()); await waitFor(() => expect(mockRead).toHaveBeenCalled());
const readResult = await mockRead.mock.results[0].value; const readResult = await mockRead.mock.results[0].value;
const policy = readResult.resource.indexingPolicy; const policy = readResult.resource.indexingPolicy;
expect(policy).toBeDefined(); expect(policy).toBeDefined();
expect(policy.automatic).toBe(true); expect(policy.automatic).toBe(true);
expect(policy.indexingMode).toBe("consistent"); expect(policy.indexingMode).toBe("consistent");
expect(policy.includedPaths).toEqual(expect.arrayContaining([{ path: "/*" }, { path: "/foo/?" }])); expect(policy.includedPaths).toEqual(expect.arrayContaining([{ path: "/*" }, { path: "/foo/?" }]));
}); });
test("refreshCollectionData updates observable and re-renders", async () => { test("refreshCollectionData updates observable and re-renders", async () => {
render(<IndexAdvisorTab />); render(<IndexAdvisorTab />);
await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("/bar/?")).toBeInTheDocument());
const checkboxes = screen.getAllByRole("checkbox"); const checkboxes = screen.getAllByRole("checkbox");
fireEvent.click(checkboxes[1]); // Select /bar/? fireEvent.click(checkboxes[1]); // Select /bar/?
fireEvent.click(screen.getByText(/Update Indexing Policy/)); fireEvent.click(screen.getByText(/Update Indexing Policy/));
await waitFor(() => expect(mockReplace).toHaveBeenCalled()); await waitFor(() => expect(mockReplace).toHaveBeenCalled());
expect(screen.getByText("/bar/?")).toBeInTheDocument(); expect(screen.getByText("/bar/?")).toBeInTheDocument();
}); });

View File

@ -71,7 +71,6 @@ export const useQueryMetadataStore = create<QueryMetadataStore>((set) => ({
setMetadata: (query1, db, container) => set({ userQuery: query1, databaseId: db, containerId: container }), setMetadata: (query1, db, container) => set({ userQuery: query1, databaseId: db, containerId: container }),
})); }));
enum ToggleState { enum ToggleState {
Result, Result,
QueryMetrics, QueryMetrics,
@ -376,9 +375,9 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
this._iterator = this.props.isPreferredApiMongoDB this._iterator = this.props.isPreferredApiMongoDB
? queryIterator(this.props.collection.databaseId, this.props.viewModelcollection, query) ? queryIterator(this.props.collection.databaseId, this.props.viewModelcollection, query)
: queryDocuments(this.props.collection.databaseId, this.props.collection.id(), query, { : queryDocuments(this.props.collection.databaseId, this.props.collection.id(), query, {
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey(), enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey(),
abortSignal: this.queryAbortController.signal, abortSignal: this.queryAbortController.signal,
} as unknown as FeedOptions); } as unknown as FeedOptions);
} }
await this._queryDocumentsPage(firstItemIndex); await this._queryDocumentsPage(firstItemIndex);

View File

@ -20,9 +20,15 @@ import {
TableColumnDefinition, TableColumnDefinition,
TableHeader, TableHeader,
TableRow, TableRow,
createTableColumn createTableColumn,
} from "@fluentui/react-components"; } from "@fluentui/react-components";
import { ArrowDownloadRegular, ChevronDown20Regular, ChevronRight20Regular, CircleFilled, CopyRegular } from "@fluentui/react-icons"; import {
ArrowDownloadRegular,
ChevronDown20Regular,
ChevronRight20Regular,
CircleFilled,
CopyRegular,
} from "@fluentui/react-icons";
import copy from "clipboard-copy"; import copy from "clipboard-copy";
import { HttpHeaders } from "Common/Constants"; import { HttpHeaders } from "Common/Constants";
import MongoUtility from "Common/MongoUtility"; import MongoUtility from "Common/MongoUtility";
@ -394,8 +400,9 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
}, },
{ {
metric: "User defined function execution time", metric: "User defined function execution time",
value: `${aggregatedQueryMetrics.runtimeExecutionTimes?.userDefinedFunctionExecutionTime?.toString() || 0 value: `${
} ms`, aggregatedQueryMetrics.runtimeExecutionTimes?.userDefinedFunctionExecutionTime?.toString() || 0
} ms`,
toolTip: "Total time spent executing user-defined functions", toolTip: "Total time spent executing user-defined functions",
}, },
{ {
@ -578,14 +585,16 @@ export const IndexAdvisorTab: React.FC = () => {
clearMessage(); clearMessage();
setLoading(false); setLoading(false);
} }
} };
if (userQuery && databaseId && containerId) { if (userQuery && databaseId && containerId) {
fetchIndexMetrics(); fetchIndexMetrics();
} }
}, [userQuery, databaseId, containerId]); }, [userQuery, databaseId, containerId]);
useEffect(() => { useEffect(() => {
if (!indexMetrics) { return } if (!indexMetrics) {
return;
}
const { included, notIncluded } = parseIndexMetrics(indexMetrics); const { included, notIncluded } = parseIndexMetrics(indexMetrics);
setIncludedIndexes(included); setIncludedIndexes(included);
@ -598,7 +607,8 @@ export const IndexAdvisorTab: React.FC = () => {
}, [indexMetrics]); }, [indexMetrics]);
useEffect(() => { useEffect(() => {
const allSelected = notIncluded.length > 0 && notIncluded.every((item) => selectedIndexes.some((s) => s.index === item.index)); const allSelected =
notIncluded.length > 0 && notIncluded.every((item) => selectedIndexes.some((s) => s.index === item.index));
setSelectAll(allSelected); setSelectAll(allSelected);
}, [selectedIndexes, notIncluded]); }, [selectedIndexes, notIncluded]);
@ -606,9 +616,7 @@ export const IndexAdvisorTab: React.FC = () => {
if (checked) { if (checked) {
setSelectedIndexes((prev) => [...prev, indexObj]); setSelectedIndexes((prev) => [...prev, indexObj]);
} else { } else {
setSelectedIndexes((prev) => setSelectedIndexes((prev) => prev.filter((item) => item.index !== indexObj.index));
prev.filter((item) => item.index !== indexObj.index)
);
} }
}; };
@ -624,32 +632,27 @@ export const IndexAdvisorTab: React.FC = () => {
const { resource: containerDef } = await containerRef.read(); const { resource: containerDef } = await containerRef.read();
const newIncludedPaths = selectedIndexes const newIncludedPaths = selectedIndexes
.filter(index => !index.composite) .filter((index) => !index.composite)
.map(index => { .map((index) => {
return { return {
path: index.path, path: index.path,
}; };
}); });
const newCompositeIndexes: CompositePath[][] = selectedIndexes const newCompositeIndexes: CompositePath[][] = selectedIndexes
.filter(index => Array.isArray(index.composite)) .filter((index) => Array.isArray(index.composite))
.map(index => .map(
(index.composite as { path: string; order: string }[]).map(comp => ({ (index) =>
path: comp.path, (index.composite as { path: string; order: string }[]).map((comp) => ({
order: comp.order === "descending" ? "descending" : "ascending", path: comp.path,
})) as CompositePath[] order: comp.order === "descending" ? "descending" : "ascending",
})) as CompositePath[],
); );
const updatedPolicy: IndexingPolicy = { const updatedPolicy: IndexingPolicy = {
...containerDef.indexingPolicy, ...containerDef.indexingPolicy,
includedPaths: [ includedPaths: [...(containerDef.indexingPolicy?.includedPaths || []), ...newIncludedPaths],
...(containerDef.indexingPolicy?.includedPaths || []), compositeIndexes: [...(containerDef.indexingPolicy?.compositeIndexes || []), ...newCompositeIndexes],
...newIncludedPaths,
],
compositeIndexes: [
...(containerDef.indexingPolicy?.compositeIndexes || []),
...newCompositeIndexes,
],
automatic: containerDef.indexingPolicy?.automatic ?? true, automatic: containerDef.indexingPolicy?.automatic ?? true,
indexingMode: containerDef.indexingPolicy?.indexingMode ?? "consistent", indexingMode: containerDef.indexingPolicy?.indexingMode ?? "consistent",
excludedPaths: containerDef.indexingPolicy?.excludedPaths ?? [], excludedPaths: containerDef.indexingPolicy?.excludedPaths ?? [],
@ -660,7 +663,7 @@ export const IndexAdvisorTab: React.FC = () => {
indexingPolicy: updatedPolicy, indexingPolicy: updatedPolicy,
}); });
useIndexingPolicyStore.getState().setIndexingPolicyFor(containerId, updatedPolicy); useIndexingPolicyStore.getState().setIndexingPolicyFor(containerId, updatedPolicy);
const selectedIndexSet = new Set(selectedIndexes.map(s => s.index)); const selectedIndexSet = new Set(selectedIndexes.map((s) => s.index));
const updatedNotIncluded: typeof notIncluded = []; const updatedNotIncluded: typeof notIncluded = [];
const newlyIncluded: typeof included = []; const newlyIncluded: typeof included = [];
for (const item of notIncluded) { for (const item of notIncluded) {
@ -686,16 +689,17 @@ export const IndexAdvisorTab: React.FC = () => {
}; };
const renderImpactDots = (impact: string) => { const renderImpactDots = (impact: string) => {
let count = 0; let count = 0;
if (impact === "High") { count = 3; } if (impact === "High") {
else if (impact === "Medium") { count = 2; } count = 3;
else if (impact === "Low") { count = 1; } } else if (impact === "Medium") {
count = 2;
} else if (impact === "Low") {
count = 1;
}
return ( return (
<div className={style.indexAdvisorImpactDots}> <div className={style.indexAdvisorImpactDots}>
{Array.from({ length: count }).map((_, i) => ( {Array.from({ length: count }).map((_, i) => (
<CircleFilled <CircleFilled key={i} className={style.indexAdvisorImpactDot} />
key={i}
className={style.indexAdvisorImpactDot}
/>
))} ))}
</div> </div>
); );
@ -712,11 +716,10 @@ export const IndexAdvisorTab: React.FC = () => {
{isNotIncluded ? ( {isNotIncluded ? (
<Checkbox <Checkbox
checked={selectedIndexes.some((selected) => selected.index === item.index)} checked={selectedIndexes.some((selected) => selected.index === item.index)}
onChange={(_, data) => handleCheckboxChange(item, data.checked === true)} /> onChange={(_, data) => handleCheckboxChange(item, data.checked === true)}
/>
) : isHeader && item.index === "Not Included in Current Policy" && notIncluded.length > 0 ? ( ) : isHeader && item.index === "Not Included in Current Policy" && notIncluded.length > 0 ? (
<Checkbox <Checkbox checked={selectAll} onChange={(_, data) => handleSelectAll(data.checked === true)} />
checked={selectAll}
onChange={(_, data) => handleSelectAll(data.checked === true)} />
) : ( ) : (
<div className={style.indexAdvisorCheckboxSpacer}></div> <div className={style.indexAdvisorCheckboxSpacer}></div>
)} )}
@ -729,24 +732,28 @@ export const IndexAdvisorTab: React.FC = () => {
} else if (item.index === "Not Included in Current Policy") { } else if (item.index === "Not Included in Current Policy") {
setShowNotIncluded(!showNotIncluded); setShowNotIncluded(!showNotIncluded);
} }
}}> }}
{item.index === "Included in Current Policy" >
? showIncluded ? <ChevronDown20Regular /> : <ChevronRight20Regular /> {item.index === "Included in Current Policy" ? (
: showNotIncluded ? <ChevronDown20Regular /> : <ChevronRight20Regular /> showIncluded ? (
} <ChevronDown20Regular />
) : (
<ChevronRight20Regular />
)
) : showNotIncluded ? (
<ChevronDown20Regular />
) : (
<ChevronRight20Regular />
)}
</span> </span>
) : ( ) : (
<div className={style.indexAdvisorChevronSpacer}></div> <div className={style.indexAdvisorChevronSpacer}></div>
)} )}
<div className={isHeader ? style.indexAdvisorRowBold : style.indexAdvisorRowNormal}> <div className={isHeader ? style.indexAdvisorRowBold : style.indexAdvisorRowNormal}>{item.index}</div>
{item.index}
</div>
<div className={isHeader ? style.indexAdvisorRowImpactHeader : style.indexAdvisorRowImpact}> <div className={isHeader ? style.indexAdvisorRowImpactHeader : style.indexAdvisorRowImpact}>
{!isHeader && item.impact} {!isHeader && item.impact}
</div> </div>
<div> <div>{!isHeader && renderImpactDots(item.impact)}</div>
{!isHeader && renderImpactDots(item.impact)}
</div>
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@ -756,29 +763,30 @@ export const IndexAdvisorTab: React.FC = () => {
const items: IIndexMetric[] = []; const items: IIndexMetric[] = [];
items.push({ index: "Not Included in Current Policy", impact: "", section: "Header" }); items.push({ index: "Not Included in Current Policy", impact: "", section: "Header" });
if (showNotIncluded) { if (showNotIncluded) {
notIncluded.forEach((item) => notIncluded.forEach((item) => items.push({ ...item, section: "Not Included" }));
items.push({ ...item, section: "Not Included" })
);
} }
items.push({ index: "Included in Current Policy", impact: "", section: "Header" }); items.push({ index: "Included in Current Policy", impact: "", section: "Header" });
if (showIncluded) { if (showIncluded) {
included.forEach((item) => included.forEach((item) => items.push({ ...item, section: "Included" }));
items.push({ ...item, section: "Included" })
);
} }
return items; return items;
}, [included, notIncluded, showIncluded, showNotIncluded]); }, [included, notIncluded, showIncluded, showNotIncluded]);
if (loading) { if (loading) {
return <div> return (
<Spinner <div>
size="small" <Spinner
style={{ size="small"
'--spinner-size': '16px', style={
'--spinner-thickness': '2px', {
'--spinner-color': '#0078D4', "--spinner-size": "16px",
} as React.CSSProperties} /> "--spinner-thickness": "2px",
</div>; "--spinner-color": "#0078D4",
} as React.CSSProperties
}
/>
</div>
);
} }
return ( return (
@ -786,12 +794,12 @@ export const IndexAdvisorTab: React.FC = () => {
<div className={style.indexAdvisorMessage}> <div className={style.indexAdvisorMessage}>
{updateMessageShown ? ( {updateMessageShown ? (
<> <>
<span <span className={style.indexAdvisorSuccessIcon}>
className={style.indexAdvisorSuccessIcon}>
<FontIcon iconName="CheckMark" style={{ color: "white", fontSize: 12 }} /> <FontIcon iconName="CheckMark" style={{ color: "white", fontSize: 12 }} />
</span> </span>
<span> <span>
Your indexing policy has been updated with the new included paths. You may review the changes in Scale & Settings. Your indexing policy has been updated with the new included paths. You may review the changes in Scale &
Settings.
</span> </span>
</> </>
) : ( ) : (
@ -807,25 +815,23 @@ export const IndexAdvisorTab: React.FC = () => {
<div className={style.indexAdvisorCheckboxSpacer}></div> <div className={style.indexAdvisorCheckboxSpacer}></div>
<div className={style.indexAdvisorChevronSpacer}></div> <div className={style.indexAdvisorChevronSpacer}></div>
<div>Index</div> <div>Index</div>
<div><span style={{ whiteSpace: "nowrap" }}>Estimated Impact</span></div> <div>
<span style={{ whiteSpace: "nowrap" }}>Estimated Impact</span>
</div>
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>{indexMetricItems.map(renderRow)}</TableBody>
{indexMetricItems.map(renderRow)}
</TableBody>
</Table> </Table>
{selectedIndexes.length > 0 && ( {selectedIndexes.length > 0 && (
<div className={style.indexAdvisorButtonBar}> <div className={style.indexAdvisorButtonBar}>
{isUpdating ? ( {isUpdating ? (
<div className={style.indexAdvisorButtonSpinner}> <div className={style.indexAdvisorButtonSpinner}>
<Spinner size="tiny" /> </div> <Spinner size="tiny" />{" "}
</div>
) : ( ) : (
<button <button onClick={handleUpdatePolicy} className={style.indexAdvisorButton}>
onClick={handleUpdatePolicy}
className={style.indexAdvisorButton}
>
Update Indexing Policy with selected index(es) Update Indexing Policy with selected index(es)
</button> </button>
)} )}
@ -895,4 +901,3 @@ export const useIndexingPolicyStore = create<IndexingPolicyStore>((set) => ({
}, },
})), })),
})); }));

View File

@ -11,6 +11,5 @@ export const useQueryMetadataStore = create<QueryMetadataStore>((set) => ({
userQuery: "", userQuery: "",
databaseId: "", databaseId: "",
containerId: "", containerId: "",
setMetadata: (query1, db, container) => setMetadata: (query1, db, container) => set({ userQuery: query1, databaseId: db, containerId: container }),
set({ userQuery: query1, databaseId: db, containerId: container }),
})); }));