mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-05-15 01:37:37 +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()`.
|
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
|
### Imports
|
||||||
|
|
||||||
TypeScript `baseUrl` is set to `src/`, so imports from `src/` are written without a leading `./src/` prefix:
|
TypeScript `baseUrl` is set to `src/`, so imports from `src/` are written without a leading `./src/` prefix:
|
||||||
|
|||||||
@@ -188,9 +188,89 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
azcliversion: latest
|
azcliversion: latest
|
||||||
inlineScript: |
|
inlineScript: |
|
||||||
NOSQL_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql.documents.azure.com/.default" -o tsv --query accessToken)
|
SHARD_INDEX=${{ matrix.shardIndex }}
|
||||||
echo "::add-mask::$NOSQL_TESTACCOUNT_TOKEN"
|
echo PLAYWRIGHT_SHARD_INDEX=$SHARD_INDEX >> $GITHUB_ENV
|
||||||
echo NOSQL_TESTACCOUNT_TOKEN=$NOSQL_TESTACCOUNT_TOKEN >> $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)
|
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 "::add-mask::$NOSQL_READONLY_TESTACCOUNT_TOKEN"
|
||||||
echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
@@ -257,4 +337,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: html-report--attempt-${{ github.run_attempt }}
|
name: html-report--attempt-${{ github.run_attempt }}
|
||||||
path: playwright-report
|
path: playwright-report
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ export interface VectorIndex {
|
|||||||
vectorIndexShardKey?: string[];
|
vectorIndexShardKey?: string[];
|
||||||
indexingSearchListSize?: number;
|
indexingSearchListSize?: number;
|
||||||
quantizationByteSize?: number;
|
quantizationByteSize?: number;
|
||||||
|
quantizerType?: "product" | "spherical";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FullTextIndex {
|
export interface FullTextIndex {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
|
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
|
||||||
import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView";
|
import { useIndexingPolicyStore } from "Explorer/Tabs/QueryTab/ResultsView";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
|
import { Keys, t } from "Localization";
|
||||||
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
||||||
@@ -44,7 +45,6 @@ import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter
|
|||||||
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
import "./SettingsComponent.less";
|
import "./SettingsComponent.less";
|
||||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||||
import { Keys, t } from "Localization";
|
|
||||||
import {
|
import {
|
||||||
ConflictResolutionComponent,
|
ConflictResolutionComponent,
|
||||||
ConflictResolutionComponentProps,
|
ConflictResolutionComponentProps,
|
||||||
@@ -555,6 +555,19 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onVectorEmbeddingPolicyChange = (newVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy): void =>
|
private onVectorEmbeddingPolicyChange = (newVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy): void =>
|
||||||
this.setState({ vectorEmbeddingPolicy: newVectorEmbeddingPolicy });
|
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 =>
|
private onFullTextPolicyChange = (newFullTextPolicy: DataModels.FullTextPolicy): void =>
|
||||||
this.setState({ fullTextPolicy: newFullTextPolicy });
|
this.setState({ fullTextPolicy: newFullTextPolicy });
|
||||||
|
|
||||||
@@ -1332,6 +1345,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
onVectorEmbeddingPolicyChange: this.onVectorEmbeddingPolicyChange,
|
onVectorEmbeddingPolicyChange: this.onVectorEmbeddingPolicyChange,
|
||||||
onVectorEmbeddingPolicyDirtyChange: this.onVectorEmbeddingPolicyDirtyChange,
|
onVectorEmbeddingPolicyDirtyChange: this.onVectorEmbeddingPolicyDirtyChange,
|
||||||
onVectorEmbeddingPolicyValidationChange: this.onVectorEmbeddingPolicyValidationChange,
|
onVectorEmbeddingPolicyValidationChange: this.onVectorEmbeddingPolicyValidationChange,
|
||||||
|
vectorIndexes: this.state.indexingPolicyContent?.vectorIndexes ?? [],
|
||||||
|
vectorIndexesBaseline: this.state.indexingPolicyContentBaseline?.vectorIndexes ?? [],
|
||||||
|
onVectorIndexesChange: this.onVectorIndexesChange,
|
||||||
isVectorSearchEnabled: this.isVectorSearchEnabled,
|
isVectorSearchEnabled: this.isVectorSearchEnabled,
|
||||||
fullTextPolicy: this.state.fullTextPolicy,
|
fullTextPolicy: this.state.fullTextPolicy,
|
||||||
fullTextPolicyBaseline: this.state.fullTextPolicyBaseline,
|
fullTextPolicyBaseline: this.state.fullTextPolicyBaseline,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DefaultButton, Pivot, PivotItem, Stack } from "@fluentui/react";
|
import { DefaultButton, Pivot, PivotItem, Stack } from "@fluentui/react";
|
||||||
import { FullTextPolicy, VectorEmbedding, VectorEmbeddingPolicy } from "Contracts/DataModels";
|
import { FullTextPolicy, VectorEmbedding, VectorEmbeddingPolicy, VectorIndex } from "Contracts/DataModels";
|
||||||
import {
|
import {
|
||||||
FullTextPoliciesComponent,
|
FullTextPoliciesComponent,
|
||||||
getFullTextLanguageOptions,
|
getFullTextLanguageOptions,
|
||||||
@@ -16,6 +16,9 @@ export interface ContainerPolicyComponentProps {
|
|||||||
onVectorEmbeddingPolicyChange: (newVectorEmbeddingPolicy: VectorEmbeddingPolicy) => void;
|
onVectorEmbeddingPolicyChange: (newVectorEmbeddingPolicy: VectorEmbeddingPolicy) => void;
|
||||||
onVectorEmbeddingPolicyDirtyChange: (isVectorEmbeddingPolicyDirty: boolean) => void;
|
onVectorEmbeddingPolicyDirtyChange: (isVectorEmbeddingPolicyDirty: boolean) => void;
|
||||||
onVectorEmbeddingPolicyValidationChange: (isValid: boolean) => void;
|
onVectorEmbeddingPolicyValidationChange: (isValid: boolean) => void;
|
||||||
|
vectorIndexes: VectorIndex[];
|
||||||
|
vectorIndexesBaseline: VectorIndex[];
|
||||||
|
onVectorIndexesChange: (newVectorIndexes: VectorIndex[]) => void;
|
||||||
isVectorSearchEnabled: boolean;
|
isVectorSearchEnabled: boolean;
|
||||||
fullTextPolicy: FullTextPolicy;
|
fullTextPolicy: FullTextPolicy;
|
||||||
fullTextPolicyBaseline: FullTextPolicy;
|
fullTextPolicyBaseline: FullTextPolicy;
|
||||||
@@ -33,6 +36,9 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
|||||||
onVectorEmbeddingPolicyChange,
|
onVectorEmbeddingPolicyChange,
|
||||||
onVectorEmbeddingPolicyDirtyChange,
|
onVectorEmbeddingPolicyDirtyChange,
|
||||||
onVectorEmbeddingPolicyValidationChange,
|
onVectorEmbeddingPolicyValidationChange,
|
||||||
|
vectorIndexes,
|
||||||
|
vectorIndexesBaseline,
|
||||||
|
onVectorIndexesChange,
|
||||||
isVectorSearchEnabled,
|
isVectorSearchEnabled,
|
||||||
fullTextPolicy,
|
fullTextPolicy,
|
||||||
fullTextPolicyBaseline,
|
fullTextPolicyBaseline,
|
||||||
@@ -78,6 +84,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
|||||||
|
|
||||||
const checkAndSendVectorEmbeddingPoliciesToSettings = (
|
const checkAndSendVectorEmbeddingPoliciesToSettings = (
|
||||||
newVectorEmbeddings: VectorEmbedding[],
|
newVectorEmbeddings: VectorEmbedding[],
|
||||||
|
newVectorIndexes: VectorIndex[],
|
||||||
validationPassed: boolean,
|
validationPassed: boolean,
|
||||||
): void => {
|
): void => {
|
||||||
onVectorEmbeddingPolicyValidationChange(validationPassed);
|
onVectorEmbeddingPolicyValidationChange(validationPassed);
|
||||||
@@ -86,6 +93,9 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
|||||||
if (isVectorDirty) {
|
if (isVectorDirty) {
|
||||||
onVectorEmbeddingPolicyChange({ vectorEmbeddings: newVectorEmbeddings });
|
onVectorEmbeddingPolicyChange({ vectorEmbeddings: newVectorEmbeddings });
|
||||||
}
|
}
|
||||||
|
if (isDirty(newVectorIndexes ?? [], vectorIndexesBaseline ?? [])) {
|
||||||
|
onVectorIndexesChange(newVectorIndexes);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkAndSendFullTextPolicyToSettings = (newFullTextPolicy: FullTextPolicy): void => {
|
const checkAndSendFullTextPolicyToSettings = (newFullTextPolicy: FullTextPolicy): void => {
|
||||||
@@ -169,12 +179,14 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
|||||||
<VectorEmbeddingPoliciesComponent
|
<VectorEmbeddingPoliciesComponent
|
||||||
vectorEmbeddingsBaseline={vectorEmbeddingsBaseline}
|
vectorEmbeddingsBaseline={vectorEmbeddingsBaseline}
|
||||||
vectorEmbeddings={vectorEmbeddings}
|
vectorEmbeddings={vectorEmbeddings}
|
||||||
vectorIndexes={undefined}
|
vectorIndexes={vectorIndexes ?? []}
|
||||||
onVectorEmbeddingChange={(
|
onVectorEmbeddingChange={(
|
||||||
vectorEmbeddings: VectorEmbedding[],
|
newVectorEmbeddings: VectorEmbedding[],
|
||||||
_vectorIndexingPolicies,
|
newVectorIndexes: VectorIndex[],
|
||||||
validationPassed: boolean,
|
validationPassed: boolean,
|
||||||
) => checkAndSendVectorEmbeddingPoliciesToSettings(vectorEmbeddings, validationPassed)}
|
) =>
|
||||||
|
checkAndSendVectorEmbeddingPoliciesToSettings(newVectorEmbeddings, newVectorIndexes, validationPassed)
|
||||||
|
}
|
||||||
discardChanges={discardVectorChanges}
|
discardChanges={discardVectorChanges}
|
||||||
onChangesDiscarded={onVectorChangesDiscarded}
|
onChangesDiscarded={onVectorChangesDiscarded}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { Keys, t } from "Localization";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { Keys, t } from "Localization";
|
|
||||||
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
|
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
|
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
|
||||||
@@ -15,6 +15,7 @@ export type isDirtyTypes =
|
|||||||
| DataModels.IndexingPolicy
|
| DataModels.IndexingPolicy
|
||||||
| DataModels.ComputedProperties
|
| DataModels.ComputedProperties
|
||||||
| DataModels.VectorEmbedding[]
|
| DataModels.VectorEmbedding[]
|
||||||
|
| DataModels.VectorIndex[]
|
||||||
| DataModels.FullTextPolicy
|
| DataModels.FullTextPolicy
|
||||||
| DataModels.ThroughputBucket[]
|
| DataModels.ThroughputBucket[]
|
||||||
| DataModels.DataMaskingPolicy;
|
| DataModels.DataMaskingPolicy;
|
||||||
|
|||||||
@@ -359,10 +359,13 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
onVectorEmbeddingPolicyChange={[Function]}
|
onVectorEmbeddingPolicyChange={[Function]}
|
||||||
onVectorEmbeddingPolicyDirtyChange={[Function]}
|
onVectorEmbeddingPolicyDirtyChange={[Function]}
|
||||||
onVectorEmbeddingPolicyValidationChange={[Function]}
|
onVectorEmbeddingPolicyValidationChange={[Function]}
|
||||||
|
onVectorIndexesChange={[Function]}
|
||||||
resetShouldDiscardContainerPolicyChange={[Function]}
|
resetShouldDiscardContainerPolicyChange={[Function]}
|
||||||
shouldDiscardContainerPolicies={false}
|
shouldDiscardContainerPolicies={false}
|
||||||
vectorEmbeddingPolicy={{}}
|
vectorEmbeddingPolicy={{}}
|
||||||
vectorEmbeddingPolicyBaseline={{}}
|
vectorEmbeddingPolicyBaseline={{}}
|
||||||
|
vectorIndexes={[]}
|
||||||
|
vectorIndexesBaseline={[]}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ import {
|
|||||||
getDataTypeOptions,
|
getDataTypeOptions,
|
||||||
getDistanceFunctionOptions,
|
getDistanceFunctionOptions,
|
||||||
getIndexTypeOptions,
|
getIndexTypeOptions,
|
||||||
|
getQuantizerTypeOptions,
|
||||||
|
supportsQuantization,
|
||||||
} from "Explorer/Controls/VectorSearch/VectorSearchUtils";
|
} from "Explorer/Controls/VectorSearch/VectorSearchUtils";
|
||||||
|
import { Keys, t } from "Localization";
|
||||||
import React, { FunctionComponent, useState } from "react";
|
import React, { FunctionComponent, useState } from "react";
|
||||||
|
|
||||||
export interface IVectorEmbeddingPoliciesComponentProps {
|
export interface IVectorEmbeddingPoliciesComponentProps {
|
||||||
@@ -46,6 +49,7 @@ export interface VectorEmbeddingPolicyData {
|
|||||||
indexingSearchListSizeError?: string;
|
indexingSearchListSizeError?: string;
|
||||||
quantizationByteSize?: number;
|
quantizationByteSize?: number;
|
||||||
quantizationByteSizeError?: string;
|
quantizationByteSizeError?: string;
|
||||||
|
quantizerType?: VectorIndex["quantizerType"];
|
||||||
}
|
}
|
||||||
|
|
||||||
type VectorEmbeddingPolicyProperty = "dataType" | "distanceFunction" | "indexType";
|
type VectorEmbeddingPolicyProperty = "dataType" | "distanceFunction" | "indexType";
|
||||||
@@ -110,7 +114,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
||||||
let error = "";
|
let error = "";
|
||||||
if (!path) {
|
if (!path) {
|
||||||
error = "Path should not be empty";
|
error = t(Keys.controls.vectorEmbeddingPolicies.pathEmptyError);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
index >= 0 &&
|
index >= 0 &&
|
||||||
@@ -119,7 +123,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
dataIndex !== index && vectorEmbedding.path === path,
|
dataIndex !== index && vectorEmbedding.path === path,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
error = "Path is already defined";
|
error = t(Keys.controls.vectorEmbeddingPolicies.pathDuplicateError);
|
||||||
}
|
}
|
||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
@@ -127,10 +131,10 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
const onVectorEmbeddingDimensionError = (dimension: number, indexType: VectorIndex["type"] | "none"): string => {
|
const onVectorEmbeddingDimensionError = (dimension: number, indexType: VectorIndex["type"] | "none"): string => {
|
||||||
let error = "";
|
let error = "";
|
||||||
if (dimension <= 0 || dimension > 4096) {
|
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) {
|
if (indexType === "flat" && dimension > 505) {
|
||||||
error = "Maximum allowed dimension for flat index is 505";
|
error = t(Keys.controls.vectorEmbeddingPolicies.dimensionFlatIndexError);
|
||||||
}
|
}
|
||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
@@ -138,7 +142,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
const onQuantizationByteSizeError = (size: number): string => {
|
const onQuantizationByteSizeError = (size: number): string => {
|
||||||
let error = "";
|
let error = "";
|
||||||
if (size < 1 || size > 512) {
|
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;
|
return error;
|
||||||
};
|
};
|
||||||
@@ -146,7 +150,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
const onIndexingSearchListSizeError = (size: number): string => {
|
const onIndexingSearchListSizeError = (size: number): string => {
|
||||||
let error = "";
|
let error = "";
|
||||||
if (size < 25 || size > 500) {
|
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;
|
return error;
|
||||||
};
|
};
|
||||||
@@ -155,11 +159,14 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
const mergedData: VectorEmbeddingPolicyData[] = [];
|
const mergedData: VectorEmbeddingPolicyData[] = [];
|
||||||
vectorEmbeddings?.forEach((embedding) => {
|
vectorEmbeddings?.forEach((embedding) => {
|
||||||
const matchingIndex = displayIndexes ? vectorIndexes.find((index) => index.path === embedding.path) : undefined;
|
const matchingIndex = displayIndexes ? vectorIndexes.find((index) => index.path === embedding.path) : undefined;
|
||||||
|
const matchingType = matchingIndex?.type;
|
||||||
|
const supportsQuantizer = supportsQuantization(matchingType);
|
||||||
mergedData.push({
|
mergedData.push({
|
||||||
...embedding,
|
...embedding,
|
||||||
indexType: matchingIndex?.type || "none",
|
indexType: matchingType || "none",
|
||||||
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
|
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
|
||||||
quantizationByteSize: matchingIndex?.quantizationByteSize || undefined,
|
quantizationByteSize: matchingIndex?.quantizationByteSize || undefined,
|
||||||
|
quantizerType: supportsQuantizer ? matchingIndex?.quantizerType || "product" : undefined,
|
||||||
vectorIndexShardKey: matchingIndex?.vectorIndexShardKey || undefined,
|
vectorIndexShardKey: matchingIndex?.vectorIndexShardKey || undefined,
|
||||||
pathError: onVectorEmbeddingPathError(embedding.path),
|
pathError: onVectorEmbeddingPathError(embedding.path),
|
||||||
dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"),
|
dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"),
|
||||||
@@ -202,6 +209,9 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
indexingSearchListSize: policy.indexingSearchListSize,
|
indexingSearchListSize: policy.indexingSearchListSize,
|
||||||
quantizationByteSize: policy.quantizationByteSize,
|
quantizationByteSize: policy.quantizationByteSize,
|
||||||
vectorIndexShardKey: policy.vectorIndexShardKey,
|
vectorIndexShardKey: policy.vectorIndexShardKey,
|
||||||
|
...(supportsQuantization(policy.indexType) && policy.quantizerType
|
||||||
|
? { quantizerType: policy.quantizerType }
|
||||||
|
: {}),
|
||||||
}) as VectorIndex,
|
}) as VectorIndex,
|
||||||
);
|
);
|
||||||
const validationPassed = vectorEmbeddingPolicyData.every(
|
const validationPassed = vectorEmbeddingPolicyData.every(
|
||||||
@@ -245,6 +255,17 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
} else {
|
} else {
|
||||||
vectorEmbedding.indexingSearchListSize = undefined;
|
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);
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,8 +327,10 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getQuantizationByteSizeTooltipContent = (): string => {
|
const getQuantizationByteSizeTooltipContent = (): string => {
|
||||||
const containerName: string = isGlobalSecondaryIndex ? "global secondary index" : "container";
|
const containerName = isGlobalSecondaryIndex
|
||||||
return `This is dynamically set by the ${containerName} if left blank, or it can be set to a fixed number`;
|
? t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSizeTooltipGlobalSecondaryIndexName)
|
||||||
|
: t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSizeTooltipContainerName);
|
||||||
|
return t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSizeTooltip, { containerName });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -319,7 +342,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||||
key={index}
|
key={index}
|
||||||
isExpandedByDefault={true}
|
isExpandedByDefault={true}
|
||||||
title={`Vector embedding ${index + 1}`}
|
title={t(Keys.controls.vectorEmbeddingPolicies.vectorEmbeddingTitle, { index: index + 1 })}
|
||||||
showDelete={true}
|
showDelete={true}
|
||||||
onDelete={() => onDelete(index)}
|
onDelete={() => onDelete(index)}
|
||||||
disableDelete={false}
|
disableDelete={false}
|
||||||
@@ -337,7 +360,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||||
Path
|
{t(Keys.controls.vectorEmbeddingPolicies.path)}
|
||||||
</Label>
|
</Label>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||||
@@ -352,7 +375,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||||
Data type
|
{t(Keys.controls.vectorEmbeddingPolicies.dataType)}
|
||||||
</Label>
|
</Label>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||||
@@ -367,7 +390,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||||
Distance function
|
{t(Keys.controls.vectorEmbeddingPolicies.distanceFunction)}
|
||||||
</Label>
|
</Label>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||||
@@ -382,7 +405,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||||
Dimensions
|
{t(Keys.controls.vectorEmbeddingPolicies.dimensions)}
|
||||||
</Label>
|
</Label>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||||
@@ -399,7 +422,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
{displayIndexes && (
|
{displayIndexes && (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
<Label disabled={isExistingPolicy(vectorEmbeddingPolicy)} styles={labelStyles}>
|
||||||
Index type
|
{t(Keys.controls.vectorEmbeddingPolicies.indexType)}
|
||||||
</Label>
|
</Label>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
disabled={isExistingPolicy(vectorEmbeddingPolicy)}
|
||||||
@@ -415,19 +438,17 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
<Label
|
<Label
|
||||||
disabled={
|
disabled={
|
||||||
isExistingPolicy(vectorEmbeddingPolicy) ||
|
isExistingPolicy(vectorEmbeddingPolicy) ||
|
||||||
(vectorEmbeddingPolicy.indexType !== "quantizedFlat" &&
|
!supportsQuantization(vectorEmbeddingPolicy.indexType)
|
||||||
vectorEmbeddingPolicy.indexType !== "diskANN")
|
|
||||||
}
|
}
|
||||||
styles={labelStyles}
|
styles={labelStyles}
|
||||||
>
|
>
|
||||||
Quantization byte size
|
{t(Keys.controls.vectorEmbeddingPolicies.quantizationByteSize)}
|
||||||
<InfoTooltip>{getQuantizationByteSizeTooltipContent()}</InfoTooltip>
|
<InfoTooltip>{getQuantizationByteSizeTooltipContent()}</InfoTooltip>
|
||||||
</Label>
|
</Label>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={
|
disabled={
|
||||||
isExistingPolicy(vectorEmbeddingPolicy) ||
|
isExistingPolicy(vectorEmbeddingPolicy) ||
|
||||||
(vectorEmbeddingPolicy.indexType !== "quantizedFlat" &&
|
!supportsQuantization(vectorEmbeddingPolicy.indexType)
|
||||||
vectorEmbeddingPolicy.indexType !== "diskANN")
|
|
||||||
}
|
}
|
||||||
id={`vector-policy-quantizationByteSize-${index + 1}`}
|
id={`vector-policy-quantizationByteSize-${index + 1}`}
|
||||||
styles={textFieldStyles}
|
styles={textFieldStyles}
|
||||||
@@ -437,6 +458,31 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</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" }}>
|
<Stack style={{ marginLeft: "10px" }}>
|
||||||
<Label
|
<Label
|
||||||
disabled={
|
disabled={
|
||||||
@@ -444,7 +490,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
}
|
}
|
||||||
styles={labelStyles}
|
styles={labelStyles}
|
||||||
>
|
>
|
||||||
Indexing search list size
|
{t(Keys.controls.vectorEmbeddingPolicies.indexingSearchListSize)}
|
||||||
</Label>
|
</Label>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={
|
disabled={
|
||||||
@@ -465,7 +511,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
}
|
}
|
||||||
styles={labelStyles}
|
styles={labelStyles}
|
||||||
>
|
>
|
||||||
Vector index shard key
|
{t(Keys.controls.vectorEmbeddingPolicies.vectorIndexShardKey)}
|
||||||
</Label>
|
</Label>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={
|
disabled={
|
||||||
@@ -484,7 +530,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
</CollapsibleSectionComponent>
|
</CollapsibleSectionComponent>
|
||||||
))}
|
))}
|
||||||
<DefaultButton id={`add-vector-policy`} styles={{ root: { maxWidth: 170, fontSize: 12 } }} onClick={onAdd}>
|
<DefaultButton id={`add-vector-policy`} styles={{ root: { maxWidth: 170, fontSize: 12 } }} onClick={onAdd}>
|
||||||
Add vector embedding
|
{t(Keys.controls.vectorEmbeddingPolicies.addVectorEmbedding)}
|
||||||
</DefaultButton>
|
</DefaultButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { IDropdownOption } from "@fluentui/react";
|
import { IDropdownOption } from "@fluentui/react";
|
||||||
|
import { VectorIndex } from "Contracts/DataModels";
|
||||||
|
import { Keys, t } from "Localization";
|
||||||
|
|
||||||
const dataTypes = ["float32", "uint8", "int8", "float16"];
|
const dataTypes = ["float32", "uint8", "int8", "float16"];
|
||||||
const distanceFunctions = ["euclidean", "cosine", "dotproduct"];
|
const distanceFunctions = ["euclidean", "cosine", "dotproduct"];
|
||||||
@@ -7,6 +9,13 @@ const indexTypes = ["none", "flat", "diskANN", "quantizedFlat"];
|
|||||||
export const getDataTypeOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(dataTypes);
|
export const getDataTypeOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(dataTypes);
|
||||||
export const getDistanceFunctionOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(distanceFunctions);
|
export const getDistanceFunctionOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(distanceFunctions);
|
||||||
export const getIndexTypeOptions = (): IDropdownOption[] => createDropdownOptionsFromLiterals(indexTypes);
|
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[] {
|
function createDropdownOptionsFromLiterals<T extends string>(literals: T[]): IDropdownOption[] {
|
||||||
return literals.map((value) => ({
|
return literals.map((value) => ({
|
||||||
|
|||||||
+3
@@ -112,6 +112,9 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
|
expect(wrapper.exists("#copyableCollectionId")).toBe(true);
|
||||||
|
expect(wrapper.find("#copyableCollectionId").hostNodes().prop("value")).toBe(selectedCollectionId);
|
||||||
|
|
||||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmCollectionId")
|
.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 { Areas } from "Common/Constants";
|
||||||
import DeleteFeedback from "Common/DeleteFeedback";
|
import DeleteFeedback from "Common/DeleteFeedback";
|
||||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||||
@@ -17,6 +17,7 @@ import React, { FunctionComponent, useState } from "react";
|
|||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
|
||||||
|
|
||||||
const themedTextFieldStyles = {
|
const themedTextFieldStyles = {
|
||||||
fieldGroup: {
|
fieldGroup: {
|
||||||
@@ -54,6 +55,10 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
|
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
const paneTitle = t(Keys.panes.deleteCollection.panelTitle, { collectionName });
|
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 onSubmit = async (): Promise<void> => {
|
||||||
const collection = useSelectedNode.getState().findSelectedCollection();
|
const collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
@@ -131,6 +136,14 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
submitButtonText: t(Keys.common.ok),
|
submitButtonText: t(Keys.common.ok),
|
||||||
onSubmit,
|
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, {
|
const confirmContainer = t(Keys.panes.deleteCollection.confirmPrompt, {
|
||||||
collectionName: collectionName.toLowerCase(),
|
collectionName: collectionName.toLowerCase(),
|
||||||
});
|
});
|
||||||
@@ -140,9 +153,29 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
t(Keys.panes.deleteCollection.feedbackReason, { collectionName });
|
t(Keys.panes.deleteCollection.feedbackReason, { collectionName });
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
|
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="confirmDeleteInput">
|
<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>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||||
{confirmContainer}
|
{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} />);
|
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
|
expect(wrapper.exists("#copyableDatabaseId")).toBe(true);
|
||||||
|
expect(wrapper.find("#copyableDatabaseId").hostNodes().prop("value")).toBe(selectedDatabaseId);
|
||||||
|
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmDatabaseId")
|
.find("#confirmDatabaseId")
|
||||||
.hostNodes()
|
.hostNodes()
|
||||||
.simulate("change", { target: { value: selectedDatabaseId } });
|
.simulate("change", { target: { value: selectedDatabaseId } });
|
||||||
expect(wrapper.exists("button")).toBe(true);
|
expect(wrapper.exists("button")).toBe(true);
|
||||||
wrapper.find("button").hostNodes().simulate("submit");
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
||||||
wrapper.unmount();
|
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 { useBoolean } from "@fluentui/react-hooks";
|
||||||
import { Areas } from "Common/Constants";
|
import { Areas } from "Common/Constants";
|
||||||
import DeleteFeedback from "Common/DeleteFeedback";
|
import DeleteFeedback from "Common/DeleteFeedback";
|
||||||
@@ -150,6 +150,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
showErrorDetails: false,
|
showErrorDetails: false,
|
||||||
message: t(Keys.panes.deleteDatabase.warningMessage),
|
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 confirmDatabase = t(Keys.panes.deleteDatabase.confirmPrompt, { databaseName: getDatabaseName() });
|
||||||
const reasonInfo =
|
const reasonInfo =
|
||||||
t(Keys.panes.deleteDatabase.feedbackTitle) +
|
t(Keys.panes.deleteDatabase.feedbackTitle) +
|
||||||
@@ -160,6 +161,25 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
{!formError && <PanelInfoErrorComponent {...errorProps} />}
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<div className="confirmDeleteInput">
|
<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>
|
<span className="mandatoryStar">* </span>
|
||||||
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||||
{confirmDatabase}
|
{confirmDatabase}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,8 @@
|
|||||||
"publish": "Publish",
|
"publish": "Publish",
|
||||||
"browse": "Browse",
|
"browse": "Browse",
|
||||||
"increaseValueBy1": "Increase value by 1",
|
"increaseValueBy1": "Increase value by 1",
|
||||||
"decreaseValueBy1": "Decrease value by 1"
|
"decreaseValueBy1": "Decrease value by 1",
|
||||||
|
"preview": "Preview"
|
||||||
},
|
},
|
||||||
"splashScreen": {
|
"splashScreen": {
|
||||||
"title": {
|
"title": {
|
||||||
@@ -420,6 +421,7 @@
|
|||||||
"deleteDatabase": {
|
"deleteDatabase": {
|
||||||
"panelTitle": "Delete {{databaseName}}",
|
"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.",
|
"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)",
|
"confirmPrompt": "Confirm by typing the {{databaseName}} id (name)",
|
||||||
"inputMismatch": "Input {{databaseName}} name \"{{input}}\" does not match the selected {{databaseName}} \"{{selectedId}}\"",
|
"inputMismatch": "Input {{databaseName}} name \"{{input}}\" does not match the selected {{databaseName}} \"{{selectedId}}\"",
|
||||||
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
||||||
@@ -427,6 +429,8 @@
|
|||||||
},
|
},
|
||||||
"deleteCollection": {
|
"deleteCollection": {
|
||||||
"panelTitle": "Delete {{collectionName}}",
|
"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",
|
"confirmPrompt": "Confirm by typing the {{collectionName}} id",
|
||||||
"inputMismatch": "Input id {{input}} does not match the selected {{selectedId}}",
|
"inputMismatch": "Input id {{input}} does not match the selected {{selectedId}}",
|
||||||
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
"feedbackTitle": "Help us improve Azure Cosmos DB!",
|
||||||
@@ -964,6 +968,29 @@
|
|||||||
"bucketOptionLabel": "Bucket {{id}} - {{percentage}}%",
|
"bucketOptionLabel": "Bucket {{id}} - {{percentage}}%",
|
||||||
"bucketNotActive": "Bucket {{id}} is not active."
|
"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 { DefaultAzureCredential } from "@azure/identity";
|
||||||
import { Frame, Locator, Page, expect } from "@playwright/test";
|
import { Frame, Locator, Page, expect } from "@playwright/test";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import { getNoSqlRbacToken } from "./NoSqlTestSetup";
|
||||||
import { TestContainerContext } from "./testData";
|
import { TestContainerContext } from "./testData";
|
||||||
|
|
||||||
const RETRY_COUNT = 3;
|
const RETRY_COUNT = 3;
|
||||||
@@ -43,17 +44,35 @@ export enum TestAccount {
|
|||||||
SQLContainerCopyOnly = "SQLContainerCopyOnly",
|
SQLContainerCopyOnly = "SQLContainerCopyOnly",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultAccounts: Record<TestAccount, string> = {
|
export function getDefaultAccountName(accountType: TestAccount): string {
|
||||||
[TestAccount.Tables]: "github-e2etests-tables",
|
switch (accountType) {
|
||||||
[TestAccount.Cassandra]: "github-e2etests-cassandra",
|
case TestAccount.Tables:
|
||||||
[TestAccount.Gremlin]: "github-e2etests-gremlin",
|
return "github-e2etests-tables";
|
||||||
[TestAccount.Mongo]: "github-e2etests-mongo",
|
case TestAccount.Cassandra:
|
||||||
[TestAccount.MongoReadonly]: "github-e2etests-mongo-readonly",
|
return "github-e2etests-cassandra";
|
||||||
[TestAccount.Mongo32]: "github-e2etests-mongo32",
|
case TestAccount.Gremlin:
|
||||||
[TestAccount.SQL]: "github-e2etests-sql",
|
return "github-e2etests-gremlin";
|
||||||
[TestAccount.SQLReadOnly]: "github-e2etests-sql-readonly",
|
case TestAccount.Mongo:
|
||||||
[TestAccount.SQLContainerCopyOnly]: "github-e2etests-sql-containercopyonly",
|
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 resourceGroupName = process.env.DE_TEST_RESOURCE_GROUP ?? "de-e2e-tests";
|
||||||
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
export const subscriptionId = process.env.DE_TEST_SUBSCRIPTION_ID ?? "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
||||||
@@ -77,7 +96,7 @@ export function getAccountName(accountType: TestAccount) {
|
|||||||
return (
|
return (
|
||||||
process.env[`DE_TEST_ACCOUNT_NAME_${accountType.toLocaleUpperCase()}`] ??
|
process.env[`DE_TEST_ACCOUNT_NAME_${accountType.toLocaleUpperCase()}`] ??
|
||||||
tryGetStandardName(accountType) ??
|
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.
|
// 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");
|
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 nosqlReadOnlyRbacToken = process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN;
|
||||||
const nosqlContainerCopyRbacToken = process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN;
|
const nosqlContainerCopyRbacToken = process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN;
|
||||||
const tableRbacToken = process.env.TABLE_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:Refresh")).toBeVisible();
|
||||||
await expect(wrapper.getByTestId("CommandBar/Button:Feedback")).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
|
// Open the Create Copy Job panel
|
||||||
await createCopyJobButton.click();
|
await createCopyJobButton.click();
|
||||||
panel = frame.getByTestId("Panel:Create copy job");
|
panel = frame.getByTestId("Panel:Create copy job");
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import {
|
|||||||
resourceGroupName,
|
resourceGroupName,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
} from "../fx";
|
} from "../fx";
|
||||||
|
import { getNoSqlRbacToken } from "../NoSqlTestSetup";
|
||||||
|
|
||||||
test("SQL account using Resource token", async ({ page }) => {
|
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.");
|
test.skip(nosqlAccountRbacToken.length > 0, "Resource tokens not supported when using data plane RBAC.");
|
||||||
|
|
||||||
const credentials = getAzureCLICredentials();
|
const credentials = getAzureCLICredentials();
|
||||||
|
|||||||
+2
-1
@@ -18,6 +18,7 @@ import {
|
|||||||
subscriptionId,
|
subscriptionId,
|
||||||
TestAccount,
|
TestAccount,
|
||||||
} from "./fx";
|
} from "./fx";
|
||||||
|
import { getNoSqlRbacToken } from "./NoSqlTestSetup";
|
||||||
|
|
||||||
// In Node.js >= 19, globalThis.crypto is already available as a read-only getter.
|
// In Node.js >= 19, globalThis.crypto is already available as a read-only getter.
|
||||||
// Only assign the polyfill for older versions.
|
// Only assign the polyfill for older versions.
|
||||||
@@ -134,7 +135,7 @@ async function createCosmosClientForSQLAccount(
|
|||||||
|
|
||||||
const rbacToken =
|
const rbacToken =
|
||||||
accountType === TestAccount.SQL
|
accountType === TestAccount.SQL
|
||||||
? process.env.NOSQL_TESTACCOUNT_TOKEN
|
? getNoSqlRbacToken()
|
||||||
: accountType === TestAccount.SQLContainerCopyOnly
|
: accountType === TestAccount.SQLContainerCopyOnly
|
||||||
? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN
|
? process.env.NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN
|
||||||
: "";
|
: "";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import "../../less/hostedexplorer.less";
|
|||||||
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
||||||
import { updateUserContext } from "../../src/UserContext";
|
import { updateUserContext } from "../../src/UserContext";
|
||||||
import { get, listKeys } from "../../src/Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { get, listKeys } from "../../src/Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
|
import { getNoSqlRbacToken } from "../NoSqlTestSetup";
|
||||||
|
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
const resourceGroup = urlSearchParams.get("resourceGroup") || process.env.RESOURCE_GROUP || "";
|
const resourceGroup = urlSearchParams.get("resourceGroup") || process.env.RESOURCE_GROUP || "";
|
||||||
@@ -15,8 +16,9 @@ const enablecontainercopy = urlSearchParams.get("enablecontainercopy");
|
|||||||
|
|
||||||
const nosqlRbacToken =
|
const nosqlRbacToken =
|
||||||
urlSearchParams.get("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 =
|
const nosqlReadOnlyRbacToken =
|
||||||
urlSearchParams.get("nosqlReadOnlyRbacToken") || process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN || "";
|
urlSearchParams.get("nosqlReadOnlyRbacToken") || process.env.NOSQL_READONLY_TESTACCOUNT_TOKEN || "";
|
||||||
const tableRbacToken = urlSearchParams.get("tableRbacToken") || process.env.TABLE_TESTACCOUNT_TOKEN || "";
|
const tableRbacToken = urlSearchParams.get("tableRbacToken") || process.env.TABLE_TESTACCOUNT_TOKEN || "";
|
||||||
@@ -70,6 +72,8 @@ const initTestExplorer = async (): Promise<void> => {
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
dataPlaneRbacEnabled: true,
|
dataPlaneRbacEnabled: true,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`No RBAC token found for test account type ${testAccountType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = await listKeys(subscriptionId, resourceGroup, accountName);
|
const keys = await listKeys(subscriptionId, resourceGroup, accountName);
|
||||||
|
|||||||
Reference in New Issue
Block a user