Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface CollapsibleSectionProps {
onDelete?: () => void;
disabled?: boolean;
disableDelete?: boolean;
testId?: string;
}

export interface CollapsibleSectionState {
Expand Down Expand Up @@ -57,6 +58,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
tabIndex={0}
role="button"
aria-expanded={this.state.isExpanded}
data-test={this.props.testId}
>
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>{this.props.title}</Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
</Label>
<Dropdown
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
id={`vector-policy-indexType-${index + 1}`}
required={true}
styles={dropdownStyles}
options={getIndexTypeOptions()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
scrollToSection("collapsibleVectorPolicySectionContent");
}}
tooltipContent={ContainerVectorPolicyTooltipContent()}
testId="container-vector-policy-section"
>
<Stack id="collapsibleVectorPolicySectionContent" styles={{ root: { position: "relative" } }}>
<Stack styles={{ root: { paddingLeft: 40 } }}>
Expand Down
4 changes: 4 additions & 0 deletions test/locale-override.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Temporary init script for Playwright MCP to override browser locale
// This runs before any page scripts, so i18next will detect 'ja' from navigator
Object.defineProperty(navigator, "language", { get: () => "ja" });
Object.defineProperty(navigator, "languages", { get: () => ["ja", "ja-JP"] });
174 changes: 173 additions & 1 deletion test/sql/container.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { expect, test } from "@playwright/test";

import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
import {
DataExplorer,
ONE_MINUTE_MS,
TEST_AUTOSCALE_THROUGHPUT_RU,
TestAccount,
generateUniqueName,
getDropdownItemByNameOrPosition,
} from "../fx";

test("SQL database and container CRUD", async ({ page }) => {
const databaseId = generateUniqueName("db");
Expand Down Expand Up @@ -50,3 +57,168 @@ test("SQL database and container CRUD", async ({ page }) => {

await expect(databaseNode.element).not.toBeAttached();
});

test.describe("Vector embedding quantizer type in New Container panel", () => {
/**
* Opens the New Container panel and expands the Container Vector Policy section.
* Returns the panel locator and the scoped vector policy content locator.
*/
const openPanelWithVectorPolicy = async (explorer: DataExplorer) => {
const newContainerButton = await explorer.globalCommandButton("New Container");
await newContainerButton.click();

const panel = explorer.panel("New Container");
await panel.waitFor();

// Expand section via its stable data-test id (avoids matching localized title text)
await explorer.frame.getByTestId("container-vector-policy-section").click();

const vectorSection = explorer.frame.locator("#collapsibleVectorPolicySectionContent");
await vectorSection.locator("#add-vector-policy").waitFor();

return { panel, vectorSection };
};

/** Closes the New Container panel without submitting via the Fluent UI Panel close button. */
const closePanel = async (explorer: DataExplorer) => {
await explorer.frame.locator("button.ms-Panel-closeButton").click();
await explorer.panel("New Container").waitFor({ state: "detached" });
};

test("Quantizer type dropdown is disabled by default when index type is none", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.SQL);
const { vectorSection } = await openPanelWithVectorPolicy(explorer);

await vectorSection.locator("#add-vector-policy").click();

// Index type defaults to "none" — quantizer type must be disabled
const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1");
await expect(quantizerDropdownBtn).toBeDisabled();

await closePanel(explorer);
});

test("Quantizer type dropdown is disabled for flat index type", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.SQL);
const { vectorSection } = await openPanelWithVectorPolicy(explorer);

await vectorSection.locator("#add-vector-policy").click();

// Select "flat" index type (does not support quantization).
// Use exact match because "flat" is also a substring of "quantizedFlat".
await vectorSection.locator("#vector-policy-indexType-1").click();
await explorer.frame.getByRole("option", { name: "flat", exact: true }).click();

const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1");
await expect(quantizerDropdownBtn).toBeDisabled();

await closePanel(explorer);
});

test("Quantizer type dropdown becomes enabled with diskANN index type and defaults to Product", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.SQL);
const { vectorSection } = await openPanelWithVectorPolicy(explorer);

await vectorSection.locator("#add-vector-policy").click();

await vectorSection.locator("#vector-policy-indexType-1").click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click();

const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1");
await expect(quantizerDropdownBtn).toBeEnabled();
await expect(quantizerDropdownBtn).toContainText("Product");

await closePanel(explorer);
});

test("Quantizer type dropdown becomes enabled with quantizedFlat index type and defaults to Product", async ({
page,
}) => {
const explorer = await DataExplorer.open(page, TestAccount.SQL);
const { vectorSection } = await openPanelWithVectorPolicy(explorer);

await vectorSection.locator("#add-vector-policy").click();

// Select "quantizedFlat" index type — supports quantization
await vectorSection.locator("#vector-policy-indexType-1").click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "quantizedFlat" })).click();

const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1");
await expect(quantizerDropdownBtn).toBeEnabled();
await expect(quantizerDropdownBtn).toContainText("Product");

await closePanel(explorer);
});

test("Quantizer type can be changed to Spherical (Preview)", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.SQL);
const { vectorSection } = await openPanelWithVectorPolicy(explorer);

await vectorSection.locator("#add-vector-policy").click();

await vectorSection.locator("#vector-policy-indexType-1").click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click();

const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1");
await quantizerDropdownBtn.click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click();

await expect(quantizerDropdownBtn).not.toContainText("Product");

await closePanel(explorer);
});

test("Creating a container with diskANN index type and Spherical quantizer type succeeds", async ({ page }) => {
const databaseId = generateUniqueName("db");
const containerId = "testvecquantizer";
const explorer = await DataExplorer.open(page, TestAccount.SQL);

await (await explorer.globalCommandButton("New Container")).click();

await explorer.whilePanelOpen(
"New Container",
async (panel, okButton) => {
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId);
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());

await explorer.frame.getByTestId("container-vector-policy-section").click();
const vectorSection = explorer.frame.locator("#collapsibleVectorPolicySectionContent");
await vectorSection.locator("#add-vector-policy").waitFor();

await vectorSection.locator("#add-vector-policy").click();
await vectorSection.locator("#vector-policy-path-1").fill("/embedding");
await vectorSection.locator("#vector-policy-dimension-1").fill("1536");

await vectorSection.locator("#vector-policy-indexType-1").click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click();

const quantizerDropdownBtn = vectorSection.locator("#vector-policy-quantizerType-1");
await expect(quantizerDropdownBtn).toBeEnabled();
await quantizerDropdownBtn.click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click();

await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);

const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
await expect(containerNode.element).toBeVisible();

// Cleanup
const databaseNode = await explorer.waitForNode(databaseId);
await databaseNode.openContextMenu();
await databaseNode.contextMenuItem("Delete Database").click();
await explorer.whilePanelOpen(
"Delete Database",
async (panel, okButton) => {
await panel.getByRole("textbox", { name: "Confirm by typing the database id" }).fill(databaseId);
await okButton.click();
},
{ closeTimeout: 5 * 60 * 1000 },
);
await expect(databaseNode.element).not.toBeAttached({ timeout: ONE_MINUTE_MS });
});
});
138 changes: 137 additions & 1 deletion test/sql/scaleAndSettings/containerPolicies/vectorPolicy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { expect, test } from "@playwright/test";
import { CommandBarButton, DataExplorer, ONE_MINUTE_MS, TestAccount } from "../../../fx";
import {
CommandBarButton,
DataExplorer,
getDropdownItemByNameOrPosition,
ONE_MINUTE_MS,
TestAccount,
} from "../../../fx";
import { createTestSQLContainer, TestContainerContext } from "../../../testData";

test.describe("Vector Policy under Scale & Settings", () => {
Expand All @@ -26,8 +32,23 @@ test.describe("Vector Policy under Scale & Settings", () => {

test.afterEach("Clear vector policies", async () => {
const { resource: containerDef } = await context.container.read();
if (!containerDef) {
return;
}

let dirty = false;

if (containerDef.vectorEmbeddingPolicy?.vectorEmbeddings?.length) {
containerDef.vectorEmbeddingPolicy.vectorEmbeddings = [];
dirty = true;
}

if (containerDef.indexingPolicy?.vectorIndexes?.length) {
containerDef.indexingPolicy.vectorIndexes = [];
dirty = true;
}

if (dirty) {
await context.container.replace(containerDef);
}
});
Expand Down Expand Up @@ -190,4 +211,119 @@ test.describe("Vector Policy under Scale & Settings", () => {
const saveButton = explorer.commandBarButton(CommandBarButton.Save);
await expect(saveButton).toBeDisabled();
});

test("Quantizer type dropdown is disabled when index type is none", async () => {
const existingCount = await getPolicyCount();

const addButton = explorer.frame.locator("#add-vector-policy");
await addButton.click();

const newIndex = existingCount + 1;

const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`);
await expect(quantizerDropdownBtn).toBeDisabled();
});

test("Quantizer type dropdown is disabled for flat index type", async () => {
const existingCount = await getPolicyCount();

const addButton = explorer.frame.locator("#add-vector-policy");
await addButton.click();

const newIndex = existingCount + 1;

await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click();
// Use exact match because "flat" is also a substring of "quantizedFlat".
await explorer.frame.getByRole("option", { name: "flat", exact: true }).click();

const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`);
await expect(quantizerDropdownBtn).toBeDisabled();
});

test("Quantizer type dropdown becomes enabled and defaults to Product for diskANN index type", async () => {
const existingCount = await getPolicyCount();

const addButton = explorer.frame.locator("#add-vector-policy");
await addButton.click();

const newIndex = existingCount + 1;

await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click();

const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`);
await expect(quantizerDropdownBtn).toBeEnabled();
await expect(quantizerDropdownBtn).toContainText("Product");
});

test("Quantizer type dropdown becomes enabled and defaults to Product for quantizedFlat index type", async () => {
const existingCount = await getPolicyCount();

const addButton = explorer.frame.locator("#add-vector-policy");
await addButton.click();

const newIndex = existingCount + 1;

await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "quantizedFlat" })).click();

const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`);
await expect(quantizerDropdownBtn).toBeEnabled();
await expect(quantizerDropdownBtn).toContainText("Product");
});

test("Quantizer type can be changed to Spherical (Preview)", async () => {
const existingCount = await getPolicyCount();

const addButton = explorer.frame.locator("#add-vector-policy");
await addButton.click();

const newIndex = existingCount + 1;

await explorer.frame.locator(`#vector-policy-path-${newIndex}`).fill("/embedding");
await explorer.frame.locator(`#vector-policy-dimension-${newIndex}`).fill("1536");

await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click();

const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`);
await quantizerDropdownBtn.click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click();

await expect(quantizerDropdownBtn).not.toContainText("Product");
});

test("Saving a vector policy with diskANN and Spherical quantizer type persists", async () => {
const existingCount = await getPolicyCount();

const addButton = explorer.frame.locator("#add-vector-policy");
await addButton.click();

const newIndex = existingCount + 1;

await explorer.frame.locator(`#vector-policy-path-${newIndex}`).fill("/vecQuantizer");
await explorer.frame.locator(`#vector-policy-dimension-${newIndex}`).fill("1536");

await explorer.frame.locator(`#vector-policy-indexType-${newIndex}`).click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { name: "diskANN" })).click();

const quantizerDropdownBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`);
await quantizerDropdownBtn.click();
await (await getDropdownItemByNameOrPosition(explorer.frame, { position: 1 })).click();

// Save
const saveButton = explorer.commandBarButton(CommandBarButton.Save);
await expect(saveButton).toBeEnabled();
await saveButton.click();
await expect(explorer.getConsoleHeaderStatus()).toContainText(`${context.container.id}`, {
timeout: 2 * ONE_MINUTE_MS,
});

await explorer.openScaleAndSettings(context);
await explorer.frame.getByTestId("settings-tab-header/ContainerVectorPolicyTab").click();
await explorer.frame.getByRole("tab", { name: "Vector Policy" }).click();

const savedQuantizerBtn = explorer.frame.locator(`#vector-policy-quantizerType-${newIndex}`);
await expect(savedQuantizerBtn).toContainText("Spherical");
});
});
Loading