mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-10-14 07:48:17 +01:00
fix:auto format
This commit is contained in:
parent
f2044f2054
commit
3949a0ecce
@ -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();
|
||||||
|
@ -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 };
|
||||||
}
|
}
|
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
@ -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) => ({
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -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 }),
|
|
||||||
}));
|
}));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user