mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-05-14 17:27:30 +01:00
Merge branch 'master' of https://github.com/Azure/cosmos-explorer into users/jawelton/remove-gallery
This commit is contained in:
@@ -109,6 +109,8 @@ const title = t("splashScreen.title.default");
|
||||
```
|
||||
The `ResourceKey` type (derived from `Resources.json`) ensures compile-time safety — invalid keys will cause a type error. When adding new strings, add the English entry to `Resources.json` first, then reference it with `t()`.
|
||||
|
||||
**Important:** Only modify the English resource file (`src/Localization/en/Resources.json`). Do not modify non-English locale files (`src/Localization/<locale>/Resources.json`) — translations are managed by a separate localization process.
|
||||
|
||||
### Imports
|
||||
|
||||
TypeScript `baseUrl` is set to `src/`, so imports from `src/` are written without a leading `./src/` prefix:
|
||||
|
||||
@@ -188,9 +188,89 @@ jobs:
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |
|
||||
NOSQL_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_TOKEN=$NOSQL_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||
SHARD_INDEX=${{ matrix.shardIndex }}
|
||||
echo PLAYWRIGHT_SHARD_INDEX=$SHARD_INDEX >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_1_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-1.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_1_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_1_TOKEN=$NOSQL_TESTACCOUNT_1_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_2_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-2.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_2_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_2_TOKEN=$NOSQL_TESTACCOUNT_2_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_3_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-3.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_3_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_3_TOKEN=$NOSQL_TESTACCOUNT_3_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_4_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-4.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_4_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_4_TOKEN=$NOSQL_TESTACCOUNT_4_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_5_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-5.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_5_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_5_TOKEN=$NOSQL_TESTACCOUNT_5_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_6_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-6.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_6_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_6_TOKEN=$NOSQL_TESTACCOUNT_6_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_7_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-7.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_7_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_7_TOKEN=$NOSQL_TESTACCOUNT_7_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_8_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-8.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_8_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_8_TOKEN=$NOSQL_TESTACCOUNT_8_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_9_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-9.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_9_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_9_TOKEN=$NOSQL_TESTACCOUNT_9_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_10_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-10.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_10_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_10_TOKEN=$NOSQL_TESTACCOUNT_10_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_11_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-11.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_11_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_11_TOKEN=$NOSQL_TESTACCOUNT_11_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_12_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-12.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_12_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_12_TOKEN=$NOSQL_TESTACCOUNT_12_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_13_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-13.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_13_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_13_TOKEN=$NOSQL_TESTACCOUNT_13_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_14_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-14.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_14_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_14_TOKEN=$NOSQL_TESTACCOUNT_14_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_15_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-15.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_15_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_15_TOKEN=$NOSQL_TESTACCOUNT_15_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_16_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-16.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_16_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_16_TOKEN=$NOSQL_TESTACCOUNT_16_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_17_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-17.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_17_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_17_TOKEN=$NOSQL_TESTACCOUNT_17_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_18_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-18.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_18_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_18_TOKEN=$NOSQL_TESTACCOUNT_18_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_19_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-19.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_19_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_19_TOKEN=$NOSQL_TESTACCOUNT_19_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_TESTACCOUNT_20_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-20.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_TESTACCOUNT_20_TOKEN"
|
||||
echo NOSQL_TESTACCOUNT_20_TOKEN=$NOSQL_TESTACCOUNT_20_TOKEN >> $GITHUB_ENV
|
||||
|
||||
NOSQL_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-readonly.documents.azure.com/.default" -o tsv --query accessToken)
|
||||
echo "::add-mask::$NOSQL_READONLY_TESTACCOUNT_TOKEN"
|
||||
echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||
@@ -257,4 +337,4 @@ jobs:
|
||||
with:
|
||||
name: html-report--attempt-${{ github.run_attempt }}
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
retention-days: 14
|
||||
|
||||
@@ -255,6 +255,7 @@ export interface VectorIndex {
|
||||
vectorIndexShardKey?: string[];
|
||||
indexingSearchListSize?: number;
|
||||
quantizationByteSize?: number;
|
||||
quantizerType?: "product" | "spherical";
|
||||
}
|
||||
|
||||
export interface FullTextIndex {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
|
||||
import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Keys, t } from "Localization";
|
||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
||||
@@ -44,7 +45,6 @@ import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter
|
||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||
import "./SettingsComponent.less";
|
||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||
import { Keys, t } from "Localization";
|
||||
import {
|
||||
ConflictResolutionComponent,
|
||||
ConflictResolutionComponentProps,
|
||||
@@ -555,6 +555,19 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private onVectorEmbeddingPolicyChange = (newVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy): void =>
|
||||
this.setState({ vectorEmbeddingPolicy: newVectorEmbeddingPolicy });
|
||||
|
||||
private onVectorIndexesChange = (newVectorIndexes: DataModels.VectorIndex[]): void => {
|
||||
const currentIndexingPolicy: DataModels.IndexingPolicy =
|
||||
this.state.indexingPolicyContent || ({} as DataModels.IndexingPolicy);
|
||||
const newIndexingPolicy: DataModels.IndexingPolicy = {
|
||||
...currentIndexingPolicy,
|
||||
vectorIndexes: newVectorIndexes,
|
||||
};
|
||||
this.setState({
|
||||
indexingPolicyContent: newIndexingPolicy,
|
||||
isIndexingPolicyDirty: true,
|
||||
});
|
||||
};
|
||||
|
||||
private onFullTextPolicyChange = (newFullTextPolicy: DataModels.FullTextPolicy): void =>
|
||||
this.setState({ fullTextPolicy: newFullTextPolicy });
|
||||
|
||||
@@ -1332,6 +1345,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
onVectorEmbeddingPolicyChange: this.onVectorEmbeddingPolicyChange,
|
||||
onVectorEmbeddingPolicyDirtyChange: this.onVectorEmbeddingPolicyDirtyChange,
|
||||
onVectorEmbeddingPolicyValidationChange: this.onVectorEmbeddingPolicyValidationChange,
|
||||
vectorIndexes: this.state.indexingPolicyContent?.vectorIndexes ?? [],
|
||||
vectorIndexesBaseline: this.state.indexingPolicyContentBaseline?.vectorIndexes ?? [],
|
||||
onVectorIndexesChange: this.onVectorIndexesChange,
|
||||
isVectorSearchEnabled: this.isVectorSearchEnabled,
|
||||
fullTextPolicy: this.state.fullTextPolicy,
|
||||
fullTextPolicyBaseline: this.state.fullTextPolicyBaseline,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DefaultButton, Pivot, PivotItem, Stack } from "@fluentui/react";
|
||||
import { FullTextPolicy, VectorEmbedding, VectorEmbeddingPolicy } from "Contracts/DataModels";
|
||||
import { FullTextPolicy, VectorEmbedding, VectorEmbeddingPolicy, VectorIndex } from "Contracts/DataModels";
|
||||
import {
|
||||
FullTextPoliciesComponent,
|
||||
getFullTextLanguageOptions,
|
||||
@@ -16,6 +16,9 @@ export interface ContainerPolicyComponentProps {
|
||||
onVectorEmbeddingPolicyChange: (newVectorEmbeddingPolicy: VectorEmbeddingPolicy) => void;
|
||||
onVectorEmbeddingPolicyDirtyChange: (isVectorEmbeddingPolicyDirty: boolean) => void;
|
||||
onVectorEmbeddingPolicyValidationChange: (isValid: boolean) => void;
|
||||
vectorIndexes: VectorIndex[];
|
||||
vectorIndexesBaseline: VectorIndex[];
|
||||
onVectorIndexesChange: (newVectorIndexes: VectorIndex[]) => void;
|
||||
isVectorSearchEnabled: boolean;
|
||||
fullTextPolicy: FullTextPolicy;
|
||||
fullTextPolicyBaseline: FullTextPolicy;
|
||||
@@ -33,6 +36,9 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
||||
onVectorEmbeddingPolicyChange,
|
||||
onVectorEmbeddingPolicyDirtyChange,
|
||||
onVectorEmbeddingPolicyValidationChange,
|
||||
vectorIndexes,
|
||||
vectorIndexesBaseline,
|
||||
onVectorIndexesChange,
|
||||
isVectorSearchEnabled,
|
||||
fullTextPolicy,
|
||||
fullTextPolicyBaseline,
|
||||
@@ -78,6 +84,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
||||
|
||||
const checkAndSendVectorEmbeddingPoliciesToSettings = (
|
||||
newVectorEmbeddings: VectorEmbedding[],
|
||||
newVectorIndexes: VectorIndex[],
|
||||
validationPassed: boolean,
|
||||
): void => {
|
||||
onVectorEmbeddingPolicyValidationChange(validationPassed);
|
||||
@@ -86,6 +93,9 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
||||
if (isVectorDirty) {
|
||||
onVectorEmbeddingPolicyChange({ vectorEmbeddings: newVectorEmbeddings });
|
||||
}
|
||||
if (isDirty(newVectorIndexes ?? [], vectorIndexesBaseline ?? [])) {
|
||||
onVectorIndexesChange(newVectorIndexes);
|
||||
}
|
||||
};
|
||||
|
||||
const checkAndSendFullTextPolicyToSettings = (newFullTextPolicy: FullTextPolicy): void => {
|
||||
@@ -169,12 +179,14 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
||||
<VectorEmbeddingPoliciesComponent
|
||||
vectorEmbeddingsBaseline={vectorEmbeddingsBaseline}
|
||||
vectorEmbeddings={vectorEmbeddings}
|
||||
vectorIndexes={undefined}
|
||||
vectorIndexes={vectorIndexes ?? []}
|
||||
onVectorEmbeddingChange={(
|
||||
vectorEmbeddings: VectorEmbedding[],
|
||||
_vectorIndexingPolicies,
|
||||
newVectorEmbeddings: VectorEmbedding[],
|
||||
newVectorIndexes: VectorIndex[],
|
||||
validationPassed: boolean,
|
||||
) => checkAndSendVectorEmbeddingPoliciesToSettings(vectorEmbeddings, validationPassed)}
|
||||
) =>
|
||||
checkAndSendVectorEmbeddingPoliciesToSettings(newVectorEmbeddings, newVectorIndexes, validationPassed)
|
||||
}
|
||||
discardChanges={discardVectorChanges}
|
||||
onChangesDiscarded={onVectorChangesDiscarded}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Keys, t } from "Localization";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Keys, t } from "Localization";
|
||||
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
|
||||
import { userContext } from "../../../UserContext";
|
||||
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
|
||||
@@ -15,6 +15,7 @@ export type isDirtyTypes =
|
||||
| DataModels.IndexingPolicy
|
||||
| DataModels.ComputedProperties
|
||||
| DataModels.VectorEmbedding[]
|
||||
| DataModels.VectorIndex[]
|
||||
| DataModels.FullTextPolicy
|
||||
| DataModels.ThroughputBucket[]
|
||||
| DataModels.DataMaskingPolicy;
|
||||
|
||||
@@ -359,10 +359,13 @@ exports[`SettingsComponent renders 1`] = `
|
||||
onVectorEmbeddingPolicyChange={[Function]}
|
||||
onVectorEmbeddingPolicyDirtyChange={[Function]}
|
||||
onVectorEmbeddingPolicyValidationChange={[Function]}
|
||||
onVectorIndexesChange={[Function]}
|
||||
resetShouldDiscardContainerPolicyChange={[Function]}
|
||||
shouldDiscardContainerPolicies={false}
|
||||
vectorEmbeddingPolicy={{}}
|
||||
vectorEmbeddingPolicyBaseline={{}}
|
||||
vectorIndexes={[]}
|
||||
vectorIndexesBaseline={[]}
|
||||
/>
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
|
||||
@@ -16,7 +16,10 @@ import {
|
||||
getDataTypeOptions,
|
||||
getDistanceFunctionOptions,
|
||||
getIndexTypeOptions,
|
||||
getQuantizerTypeOptions,
|
||||
supportsQuantization,
|
||||
} from "Explorer/Controls/VectorSearch/VectorSearchUtils";
|
||||
import { Keys, t } from "Localization";
|
||||
import React, { FunctionComponent, useState } from "react";
|
||||
|
||||
export interface IVectorEmbeddingPoliciesComponentProps {
|
||||
@@ -46,6 +49,7 @@ export interface VectorEmbeddingPolicyData {
|
||||
indexingSearchListSizeError?: string;
|
||||
quantizationByteSize?: number;
|
||||
quantizationByteSizeError?: string;
|
||||
quantizerType?: VectorIndex["quantizerType"];
|
||||
}
|
||||
|
||||
type VectorEmbeddingPolicyProperty = "dataType" | "distanceFunction" | "indexType";
|
||||
@@ -110,7 +114,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
||||
let error = "";
|
||||
if (!path) {
|
||||
error = "Path should not be empty";
|
||||
error = t(Keys.controls.vectorEmbeddingPolicies.pathEmptyError);
|
||||
}
|
||||
if (
|
||||
index >= 0 &&
|
||||
@@ -119,7 +123,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
dataIndex !== index && vectorEmbedding.path === path,
|
||||
)
|
||||
) {
|
||||
error = "Path is already defined";
|
||||
error = t(Keys.controls.vectorEmbeddingPolicies.pathDuplicateError);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
@@ -127,10 +131,10 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
const onVectorEmbeddingDimensionError = (dimension: number, indexType: VectorIndex["type"] | "none"): string => {
|
||||
let error = "";
|
||||
if (dimension <= 0 || dimension > 4096) {
|
||||
error = "Dimension must be greater than 0 and less than or equal 4096";
|
||||
error = t(Keys.controls.vectorEmbeddingPolicies.dimensionRangeError);
|
||||
}
|
||||
if (indexType === "flat" && dimension > 505) {
|
||||
error = "Maximum allowed dimension for flat index is 505";
|
||||
error = t(Keys.controls.vectorEmbeddingPolicies.dimensionFlatIndexError);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
@@ -138,7 +142,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
const onQuantizationByteSizeError = (size: number): string => {
|
||||
let error = "";
|
||||
if (size < 1 || size > 512) {
|
||||
error = "Quantization byte size must be greater than 0 and less than or equal to 512";
|
||||
error = t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSizeRangeError);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
@@ -146,7 +150,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
const onIndexingSearchListSizeError = (size: number): string => {
|
||||
let error = "";
|
||||
if (size < 25 || size > 500) {
|
||||
error = "Indexing search list size must be greater than or equal to 25 and less than or equal to 500";
|
||||
error = t(Keys.controls.vectorEmbeddingPolicies.indexingSearchListSizeRangeError);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
@@ -155,11 +159,14 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
const mergedData: VectorEmbeddingPolicyData[] = [];
|
||||
vectorEmbeddings?.forEach((embedding) => {
|
||||
const matchingIndex = displayIndexes ? vectorIndexes.find((index) => index.path === embedding.path) : undefined;
|
||||
const matchingType = matchingIndex?.type;
|
||||
const supportsQuantizer = supportsQuantization(matchingType);
|
||||
mergedData.push({
|
||||
...embedding,
|
||||
indexType: matchingIndex?.type || "none",
|
||||
indexType: matchingType || "none",
|
||||
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
|
||||
quantizationByteSize: matchingIndex?.quantizationByteSize || undefined,
|
||||
quantizerType: supportsQuantizer ? matchingIndex?.quantizerType || "product" : undefined,
|
||||
vectorIndexShardKey: matchingIndex?.vectorIndexShardKey || undefined,
|
||||
pathError: onVectorEmbeddingPathError(embedding.path),
|
||||
dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"),
|
||||
@@ -202,6 +209,9 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
indexingSearchListSize: policy.indexingSearchListSize,
|
||||
quantizationByteSize: policy.quantizationByteSize,
|
||||
vectorIndexShardKey: policy.vectorIndexShardKey,
|
||||
...(supportsQuantization(policy.indexType) && policy.quantizerType
|
||||
? { quantizerType: policy.quantizerType }
|
||||
: {}),
|
||||
}) as VectorIndex,
|
||||
);
|
||||
const validationPassed = vectorEmbeddingPolicyData.every(
|
||||
@@ -245,6 +255,17 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
} else {
|
||||
vectorEmbedding.indexingSearchListSize = undefined;
|
||||
}
|
||||
if (supportsQuantization(vectorEmbedding.indexType)) {
|
||||
vectorEmbedding.quantizerType = vectorEmbedding.quantizerType || "product";
|
||||
} else {
|
||||
vectorEmbedding.quantizerType = undefined;
|
||||
}
|
||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||
};
|
||||
|
||||
const onQuantizerTypeChange = (index: number, option: IDropdownOption): void => {
|
||||
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||
vectorEmbeddings[index].quantizerType = option.key as VectorIndex["quantizerType"];
|
||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||
};
|
||||
|
||||
@@ -306,8 +327,10 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
};
|
||||
|
||||
const getQuantizationByteSizeTooltipContent = (): string => {
|
||||
const containerName: string = isGlobalSecondaryIndex ? "global secondary index" : "container";
|
||||
return `This is dynamically set by the ${containerName} if left blank, or it can be set to a fixed number`;
|
||||
const containerName = isGlobalSecondaryIndex
|
||||
? t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSizeTooltipGlobalSecondaryIndexName)
|
||||
: t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSizeTooltipContainerName);
|
||||
return t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSizeTooltip, { containerName });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -319,7 +342,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||
key={index}
|
||||
isExpandedByDefault={true}
|
||||
title={`Vector embedding ${index + 1}`}
|
||||
title={t(Keys.controls.vectorEmbeddingPolicies.vectorEmbeddingTitle, { index: index + 1 })}
|
||||
showDelete={true}
|
||||
onDelete={() => onDelete(index)}
|
||||
disableDelete={false}
|
||||
@@ -337,7 +360,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
>
|
||||
<Stack>
|
||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||
Path
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.path)}
|
||||
</Label>
|
||||
<TextField
|
||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||
@@ -352,7 +375,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||
Data type
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.dataType)}
|
||||
</Label>
|
||||
<Dropdown
|
||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||
@@ -367,7 +390,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||
Distance function
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.distanceFunction)}
|
||||
</Label>
|
||||
<Dropdown
|
||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||
@@ -382,7 +405,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||
Dimensions
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.dimensions)}
|
||||
</Label>
|
||||
<TextField
|
||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||
@@ -399,7 +422,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
{displayIndexes && (
|
||||
<Stack>
|
||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||
Index type
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.indexType)}
|
||||
</Label>
|
||||
<Dropdown
|
||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||
@@ -415,19 +438,17 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
<Label
|
||||
disabled={
|
||||
isExistingPolicy(vectorEmbeddingPolicy) ||
|
||||
(vectorEmbeddingPolicy.indexType !== "quantizedFlat" &&
|
||||
vectorEmbeddingPolicy.indexType !== "diskANN")
|
||||
!supportsQuantization(vectorEmbeddingPolicy.indexType)
|
||||
}
|
||||
styles={labelStyles}
|
||||
>
|
||||
Quantization byte size
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSize)}
|
||||
<InfoTooltip>{getQuantizationByteSizeTooltipContent()}</InfoTooltip>
|
||||
</Label>
|
||||
<TextField
|
||||
disabled={
|
||||
isExistingPolicy(vectorEmbeddingPolicy) ||
|
||||
(vectorEmbeddingPolicy.indexType !== "quantizedFlat" &&
|
||||
vectorEmbeddingPolicy.indexType !== "diskANN")
|
||||
!supportsQuantization(vectorEmbeddingPolicy.indexType)
|
||||
}
|
||||
id={`vector-policy-quantizationByteSize-${index + 1}`}
|
||||
styles={textFieldStyles}
|
||||
@@ -437,6 +458,31 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack style={{ marginLeft: "10px" }}>
|
||||
<Label
|
||||
disabled={
|
||||
isExistingPolicy(vectorEmbeddingPolicy) ||
|
||||
!supportsQuantization(vectorEmbeddingPolicy.indexType)
|
||||
}
|
||||
styles={labelStyles}
|
||||
>
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.quantizerType)}
|
||||
<InfoTooltip>{t(Keys.controls.vectorEmbeddingPolicies.quantizerTypeTooltip)}</InfoTooltip>
|
||||
</Label>
|
||||
<Dropdown
|
||||
disabled={
|
||||
isExistingPolicy(vectorEmbeddingPolicy) ||
|
||||
!supportsQuantization(vectorEmbeddingPolicy.indexType)
|
||||
}
|
||||
id={`vector-policy-quantizerType-${index + 1}`}
|
||||
styles={dropdownStyles}
|
||||
options={getQuantizerTypeOptions()}
|
||||
selectedKey={vectorEmbeddingPolicy.quantizerType ?? null}
|
||||
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||
onQuantizerTypeChange(index, option)
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack style={{ marginLeft: "10px" }}>
|
||||
<Label
|
||||
disabled={
|
||||
@@ -444,7 +490,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
}
|
||||
styles={labelStyles}
|
||||
>
|
||||
Indexing search list size
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.indexingSearchListSize)}
|
||||
</Label>
|
||||
<TextField
|
||||
disabled={
|
||||
@@ -465,7 +511,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
}
|
||||
styles={labelStyles}
|
||||
>
|
||||
Vector index shard key
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.vectorIndexShardKey)}
|
||||
</Label>
|
||||
<TextField
|
||||
disabled={
|
||||
@@ -484,7 +530,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
||||
</CollapsibleSectionComponent>
|
||||
))}
|
||||
<DefaultButton id={`add-vector-policy`} styles={{ root: { maxWidth: 170, fontSize: 12 } }} onClick={onAdd}>
|
||||
Add vector embedding
|
||||
{t(Keys.controls.vectorEmbeddingPolicies.addVectorEmbedding)}
|
||||
</DefaultButton>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { IDropdownOption } from "@fluentui/react";
|
||||
import { VectorIndex } from "Contracts/DataModels";
|
||||
import { Keys, t } from "Localization";
|
||||
|
||||
const dataTypes = ["float32", "uint8", "int8", "float16"];
|
||||
const distanceFunctions = ["euclidean", "cosine", "dotproduct"];
|
||||
@@ -7,6 +9,13 @@ const indexTypes = ["none", "flat", "diskANN", "quantizedFlat"];
|
||||
export const getDataTypeOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(dataTypes);
|
||||
export const getDistanceFunctionOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(distanceFunctions);
|
||||
export const getIndexTypeOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(indexTypes);
|
||||
export const getQuantizerTypeOptions = (): IDropdownOption[] => [
|
||||
{ key: "product", text: "Product" },
|
||||
{ key: "spherical", text: `Spherical (${t(Keys.common.preview)})` },
|
||||
];
|
||||
|
||||
export const supportsQuantization = (indexType: VectorIndex["type"] | "none" | undefined): boolean =>
|
||||
indexType === "quantizedFlat" || indexType === "diskANN";
|
||||
|
||||
function createDropdownOptionsFromLiterals<T extends string>(literals: T[]): IDropdownOption[] {
|
||||
return literals.map((value) => ({
|
||||
|
||||
+3
@@ -112,6 +112,9 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
expect(wrapper.exists("#copyableCollectionId")).toBe(true);
|
||||
expect(wrapper.find("#copyableCollectionId").hostNodes().prop("value")).toBe(selectedCollectionId);
|
||||
|
||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||
wrapper
|
||||
.find("#confirmCollectionId")
|
||||
|
||||
+34
-1
@@ -1,4 +1,4 @@
|
||||
import { Text, TextField } from "@fluentui/react";
|
||||
import { IconButton, Text, TextField } from "@fluentui/react";
|
||||
import { Areas } from "Common/Constants";
|
||||
import DeleteFeedback from "Common/DeleteFeedback";
|
||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||
@@ -17,6 +17,7 @@ import React, { FunctionComponent, useState } from "react";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
|
||||
|
||||
const themedTextFieldStyles = {
|
||||
fieldGroup: {
|
||||
@@ -54,6 +55,10 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
|
||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||
const paneTitle = t(Keys.panes.deleteCollection.panelTitle, { collectionName });
|
||||
const selectedCollection = useSelectedNode.getState().selectedNode
|
||||
? useSelectedNode.getState().findSelectedCollection()
|
||||
: undefined;
|
||||
const selectedCollectionId = selectedCollection?.id() ?? "";
|
||||
|
||||
const onSubmit = async (): Promise<void> => {
|
||||
const collection = useSelectedNode.getState().findSelectedCollection();
|
||||
@@ -131,6 +136,14 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
submitButtonText: t(Keys.common.ok),
|
||||
onSubmit,
|
||||
};
|
||||
const errorProps: PanelInfoErrorProps = {
|
||||
messageType: "warning",
|
||||
showErrorDetails: false,
|
||||
message: t(Keys.panes.deleteCollection.warningMessage),
|
||||
};
|
||||
const copyableIdLabel = t(Keys.panes.deleteCollection.copyableId, {
|
||||
collectionName: getCollectionName(),
|
||||
});
|
||||
const confirmContainer = t(Keys.panes.deleteCollection.confirmPrompt, {
|
||||
collectionName: collectionName.toLowerCase(),
|
||||
});
|
||||
@@ -140,9 +153,29 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||
t(Keys.panes.deleteCollection.feedbackReason, { collectionName });
|
||||
return (
|
||||
<RightPaneForm {...props}>
|
||||
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||
<div className="panelFormWrapper">
|
||||
<div className="panelMainContent">
|
||||
<div className="confirmDeleteInput">
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{copyableIdLabel}
|
||||
</Text>
|
||||
<TextField
|
||||
id="copyableCollectionId"
|
||||
readOnly
|
||||
value={selectedCollectionId}
|
||||
styles={themedTextFieldStyles}
|
||||
onRenderSuffix={() => (
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
title={t(Keys.common.copy)}
|
||||
ariaLabel={t(Keys.common.copy)}
|
||||
onClick={() => navigator.clipboard.writeText(selectedCollectionId)}
|
||||
styles={{ root: { height: "100%" } }}
|
||||
/>
|
||||
)}
|
||||
ariaLabel={copyableIdLabel}
|
||||
/>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{confirmContainer}
|
||||
|
||||
+1861
-10
File diff suppressed because it is too large
Load Diff
@@ -62,13 +62,15 @@ describe("Delete Database Confirmation Pane", () => {
|
||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||
expect(wrapper.exists("#copyableDatabaseId")).toBe(true);
|
||||
expect(wrapper.find("#copyableDatabaseId").hostNodes().prop("value")).toBe(selectedDatabaseId);
|
||||
|
||||
wrapper
|
||||
.find("#confirmDatabaseId")
|
||||
.hostNodes()
|
||||
.simulate("change", { target: { value: selectedDatabaseId } });
|
||||
expect(wrapper.exists("button")).toBe(true);
|
||||
wrapper.find("button").hostNodes().simulate("submit");
|
||||
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Text, TextField } from "@fluentui/react";
|
||||
import { IconButton, Text, TextField } from "@fluentui/react";
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import { Areas } from "Common/Constants";
|
||||
import DeleteFeedback from "Common/DeleteFeedback";
|
||||
@@ -150,6 +150,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
showErrorDetails: false,
|
||||
message: t(Keys.panes.deleteDatabase.warningMessage),
|
||||
};
|
||||
const copyableIdLabel = t(Keys.panes.deleteDatabase.copyableId, { databaseName: getDatabaseName() });
|
||||
const confirmDatabase = t(Keys.panes.deleteDatabase.confirmPrompt, { databaseName: getDatabaseName() });
|
||||
const reasonInfo =
|
||||
t(Keys.panes.deleteDatabase.feedbackTitle) +
|
||||
@@ -160,6 +161,25 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||
<div className="panelMainContent">
|
||||
<div className="confirmDeleteInput">
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{copyableIdLabel}
|
||||
</Text>
|
||||
<TextField
|
||||
id="copyableDatabaseId"
|
||||
readOnly
|
||||
value={selectedDatabase?.id() ?? ""}
|
||||
styles={themedTextFieldStyles}
|
||||
onRenderSuffix={() => (
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Copy" }}
|
||||
title={t(Keys.common.copy)}
|
||||
ariaLabel={t(Keys.common.copy)}
|
||||
onClick={() => navigator.clipboard.writeText(selectedDatabase?.id() ?? "")}
|
||||
styles={{ root: { height: "100%" } }}
|
||||
/>
|
||||
)}
|
||||
ariaLabel={copyableIdLabel}
|
||||
/>
|
||||
<span className="mandatoryStar">* </span>
|
||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||
{confirmDatabase}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,8 @@
|
||||
"publish": "Publish",
|
||||
"browse": "Browse",
|
||||
"increaseValueBy1": "Increase value by 1",
|
||||
"decreaseValueBy1": "Decrease value by 1"
|
||||
"decreaseValueBy1": "Decrease value by 1",
|
||||
"preview": "Preview"
|
||||
},
|
||||
"splashScreen": {
|
||||
"title": {
|
||||
@@ -420,6 +421,7 @@
|
||||
"deleteDatabase": {
|
||||
"panelTitle": "Delete {{databaseName}}",
|
||||
"warningMessage": "Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||
"copyableId": "{{databaseName}} Id:",
|
||||
"confirmPrompt": "Confirm by typing the {{databaseName}} id (name)",
|
||||
"inputMismatch": "Input {{databaseName}} name \"{{input}}\" does not match the selected {{databaseName}} \"{{selectedId}}\"",
|
||||
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
||||
@@ -427,6 +429,8 @@
|
||||
},
|
||||
"deleteCollection": {
|
||||
"panelTitle": "Delete {{collectionName}}",
|
||||
"warningMessage": "Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||
"copyableId": "{{collectionName}} Id:",
|
||||
"confirmPrompt": "Confirm by typing the {{collectionName}} id",
|
||||
"inputMismatch": "Input id {{input}} does not match the selected {{selectedId}}",
|
||||
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
||||
@@ -964,6 +968,29 @@
|
||||
"bucketOptionLabel": "Bucket {{id}} - {{percentage}}%",
|
||||
"bucketNotActive": "Bucket {{id}} is not active."
|
||||
}
|
||||
},
|
||||
"vectorEmbeddingPolicies": {
|
||||
"vectorEmbeddingTitle": "Vector embedding {{index}}",
|
||||
"path": "Path",
|
||||
"dataType": "Data type",
|
||||
"distanceFunction": "Distance function",
|
||||
"dimensions": "Dimensions",
|
||||
"indexType": "Index type",
|
||||
"quantizationByteSize": "Quantization byte size",
|
||||
"quantizationByteSizeTooltip": "This is dynamically set by the {{containerName}} if left blank, or it can be set to a fixed number",
|
||||
"quantizationByteSizeTooltipContainerName": "container",
|
||||
"quantizationByteSizeTooltipGlobalSecondaryIndexName": "global secondary index",
|
||||
"quantizerType": "Quantizer type",
|
||||
"quantizerTypeTooltip": "The quantization method used by the vector index.",
|
||||
"indexingSearchListSize": "Indexing search list size",
|
||||
"vectorIndexShardKey": "Vector index shard key",
|
||||
"addVectorEmbedding": "Add vector embedding",
|
||||
"pathEmptyError": "Path should not be empty",
|
||||
"pathDuplicateError": "Path is already defined",
|
||||
"dimensionRangeError": "Dimension must be greater than 0 and less than or equal 4096",
|
||||
"dimensionFlatIndexError": "Maximum allowed dimension for flat index is 505",
|
||||
"quantizationByteSizeRangeError": "Quantization byte size must be greater than 0 and less than or equal to 512",
|
||||
"indexingSearchListSizeRangeError": "Indexing search list size must be greater than or equal to 25 and less than or equal to 500"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
export function getNoSqlRbacToken(): string | undefined {
|
||||
let nosqlRbacToken: string | undefined;
|
||||
const shardIndex = process.env.PLAYWRIGHT_SHARD_INDEX ?? "";
|
||||
switch (parseInt(shardIndex)) {
|
||||
case 1:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_1_TOKEN;
|
||||
break;
|
||||
case 2:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_2_TOKEN;
|
||||
break;
|
||||
case 3:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_3_TOKEN;
|
||||
break;
|
||||
case 4:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_4_TOKEN;
|
||||
break;
|
||||
case 5:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_5_TOKEN;
|
||||
break;
|
||||
case 6:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_6_TOKEN;
|
||||
break;
|
||||
case 7:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_7_TOKEN;
|
||||
break;
|
||||
case 8:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_8_TOKEN;
|
||||
break;
|
||||
case 9:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_9_TOKEN;
|
||||
break;
|
||||
case 10:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_10_TOKEN;
|
||||
break;
|
||||
case 11:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_11_TOKEN;
|
||||
break;
|
||||
case 12:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_12_TOKEN;
|
||||
break;
|
||||
case 13:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_13_TOKEN;
|
||||
break;
|
||||
case 14:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_14_TOKEN;
|
||||
break;
|
||||
case 15:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_15_TOKEN;
|
||||
break;
|
||||
case 16:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_16_TOKEN;
|
||||
break;
|
||||
case 17:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_17_TOKEN;
|
||||
break;
|
||||
case 18:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_18_TOKEN;
|
||||
break;
|
||||
case 19:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_19_TOKEN;
|
||||
break;
|
||||
case 20:
|
||||
nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_20_TOKEN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!nosqlRbacToken) {
|
||||
console.warn(`No NoSQL RBAC token found for shard index ${shardIndex}`);
|
||||
}
|
||||
return nosqlRbacToken;
|
||||
}
|
||||
+36
-13
@@ -1,6 +1,7 @@
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
import { Frame, Locator, Page, expect } from "@playwright/test";
|
||||
import crypto from "crypto";
|
||||
import { getNoSqlRbacToken } from "./NoSqlTestSetup";
|
||||
import { TestContainerContext } from "./testData";
|
||||
|
||||
const RETRY_COUNT = 3;
|
||||
@@ -43,17 +44,35 @@ export enum TestAccount {
|
||||
SQLContainerCopyOnly = "SQLContainerCopyOnly",
|
||||
}
|
||||
|
||||
export const defaultAccounts: Record<TestAccount, string> = {
|
||||
[TestAccount.Tables]: "github-e2etests-tables",
|
||||
[TestAccount.Cassandra]: "github-e2etests-cassandra",
|
||||
[TestAccount.Gremlin]: "github-e2etests-gremlin",
|
||||
[TestAccount.Mongo]: "github-e2etests-mongo",
|
||||
[TestAccount.MongoReadonly]: "github-e2etests-mongo-readonly",
|
||||
[TestAccount.Mongo32]: "github-e2etests-mongo32",
|
||||
[TestAccount.SQL]: "github-e2etests-sql",
|
||||
[TestAccount.SQLReadOnly]: "github-e2etests-sql-readonly",
|
||||
[TestAccount.SQLContainerCopyOnly]: "github-e2etests-sql-containercopyonly",
|
||||
};
|
||||
export function getDefaultAccountName(accountType: TestAccount): string {
|
||||
switch (accountType) {
|
||||
case TestAccount.Tables:
|
||||
return "github-e2etests-tables";
|
||||
case TestAccount.Cassandra:
|
||||
return "github-e2etests-cassandra";
|
||||
case TestAccount.Gremlin:
|
||||
return "github-e2etests-gremlin";
|
||||
case TestAccount.Mongo:
|
||||
return "github-e2etests-mongo";
|
||||
case TestAccount.MongoReadonly:
|
||||
return "github-e2etests-mongo-readonly";
|
||||
case TestAccount.Mongo32:
|
||||
return "github-e2etests-mongo32";
|
||||
case TestAccount.SQLReadOnly:
|
||||
return "github-e2etests-sql-readonly";
|
||||
case TestAccount.SQLContainerCopyOnly:
|
||||
return "github-e2etests-sql-containercopyonly";
|
||||
case TestAccount.SQL: {
|
||||
const shardIndex = process.env.PLAYWRIGHT_SHARD_INDEX ?? "";
|
||||
if (!shardIndex) {
|
||||
throw new Error("PLAYWRIGHT_SHARD_INDEX is not set");
|
||||
}
|
||||
return "github-e2etests-sql-" + shardIndex;
|
||||
}
|
||||
default:
|
||||
throw new Error(`No default account name defined for account type ${accountType}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
|
||||
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
||||
@@ -77,7 +96,7 @@ export function getAccountName(accountType: TestAccount) {
|
||||
return (
|
||||
process.env[`DE_TEST_ACCOUNT_NAME_${accountType.toLocaleUpperCase()}`] ??
|
||||
tryGetStandardName(accountType) ??
|
||||
defaultAccounts[accountType]
|
||||
getDefaultAccountName(accountType)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,7 +121,11 @@ export async function getTestExplorerUrl(accountType: TestAccount, options?: Tes
|
||||
// For now, since we don't test copilot, we can disable the copilot APIs by setting the feature flag to false.
|
||||
params.set("feature.enableCopilot", "false");
|
||||
|
||||
const nosqlRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN;
|
||||
const nosqlRbacToken = getNoSqlRbacToken();
|
||||
if (!nosqlRbacToken) {
|
||||
throw new Error("No NOSQL RBAC token found.");
|
||||
}
|
||||
|
||||
const nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN;
|
||||
const nosqlContainerCopyRbacToken = process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN;
|
||||
const tableRbacToken = process.env.TABLE_TESTACCOUNT_TOKEN;
|
||||
|
||||
@@ -35,6 +35,36 @@ test.describe("Container Copy - Permission Screen Verification", () => {
|
||||
await expect(wrapper.getByTestId("CommandBar/Button:Refresh")).toBeVisible();
|
||||
await expect(wrapper.getByTestId("CommandBar/Button:Feedback")).toBeVisible();
|
||||
|
||||
// Mock Resource Graph API — fires on auto-subscription selection to populate account dropdown
|
||||
await page.route(
|
||||
"https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01",
|
||||
async (route) => {
|
||||
const request = route.request();
|
||||
if (
|
||||
request.method() === "POST" &&
|
||||
(request.postDataJSON()?.query as string) ===
|
||||
"resources | where type =~ 'microsoft.documentdb/databaseaccounts'"
|
||||
) {
|
||||
const response = await route.fetch();
|
||||
const responseData = await response.json();
|
||||
if (responseData.data && Array.isArray(responseData.data)) {
|
||||
responseData.data = responseData.data.map((d: any) => {
|
||||
d.properties.backupPolicy.type = "Periodic";
|
||||
return d;
|
||||
});
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(responseData),
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
},
|
||||
{ times: 2 },
|
||||
);
|
||||
|
||||
// Open the Create Copy Job panel
|
||||
await createCopyJobButton.click();
|
||||
panel = frame.getByTestId("Panel:Create copy job");
|
||||
|
||||
@@ -11,9 +11,10 @@ import {
|
||||
resourceGroupName,
|
||||
subscriptionId,
|
||||
} from "../fx";
|
||||
import { getNoSqlRbacToken } from "../NoSqlTestSetup";
|
||||
|
||||
test("SQL account using Resource token", async ({ page }) => {
|
||||
const nosqlAccountRbacToken = process.env.NOSQL_TESTACCOUNT_TOKEN || "";
|
||||
const nosqlAccountRbacToken = getNoSqlRbacToken() ?? "";
|
||||
test.skip(nosqlAccountRbacToken.length > 0, "Resource tokens not supported when using data plane RBAC.");
|
||||
|
||||
const credentials = getAzureCLICredentials();
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ import {
|
||||
subscriptionId,
|
||||
TestAccount,
|
||||
} from "./fx";
|
||||
import { getNoSqlRbacToken } from "./NoSqlTestSetup";
|
||||
|
||||
// In Node.js >= 19, globalThis.crypto is already available as a read-only getter.
|
||||
// Only assign the polyfill for older versions.
|
||||
@@ -134,7 +135,7 @@ async function createCosmosClientForSQLAccount(
|
||||
|
||||
const rbacToken =
|
||||
accountType === TestAccount.SQL
|
||||
? process.env.NOSQL_TESTACCOUNT_TOKEN
|
||||
? getNoSqlRbacToken()
|
||||
: accountType === TestAccount.SQLContainerCopyOnly
|
||||
? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN
|
||||
: "";
|
||||
|
||||
@@ -3,6 +3,7 @@ import "../../less/hostedexplorer.less";
|
||||
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
||||
import { updateUserContext } from "../../src/UserContext";
|
||||
import { get, listKeys } from "../../src/Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||
import { getNoSqlRbacToken } from "../NoSqlTestSetup";
|
||||
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const resourceGroup = urlSearchParams.get("resourceGroup") || process.env.RESOURCE_GROUP || "";
|
||||
@@ -15,8 +16,9 @@ const enablecontainercopy = urlSearchParams.get("enablecontainercopy");
|
||||
|
||||
const nosqlRbacToken =
|
||||
urlSearchParams.get("nosqlRbacToken") ||
|
||||
(enablecontainercopy ? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN : process.env.NOSQL_TESTACCOUNT_TOKEN) ||
|
||||
(enablecontainercopy ? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN : getNoSqlRbacToken()) ||
|
||||
"";
|
||||
|
||||
const nosqlReadOnlyRbacToken =
|
||||
urlSearchParams.get("nosqlReadOnlyRbacToken") || process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN || "";
|
||||
const tableRbacToken = urlSearchParams.get("tableRbacToken") || process.env.TABLE_TESTACCOUNT_TOKEN || "";
|
||||
@@ -70,6 +72,8 @@ const initTestExplorer = async (): Promise<void> => {
|
||||
updateUserContext({
|
||||
dataPlaneRbacEnabled: true,
|
||||
});
|
||||
} else {
|
||||
console.error(`No RBAC token found for test account type ${testAccountType}`);
|
||||
}
|
||||
|
||||
const keys = await listKeys(subscriptionId, resourceGroup, accountName);
|
||||
|
||||
Reference in New Issue
Block a user