mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-31 23:02:29 +00:00
Compare commits
12 Commits
users/lang
...
release/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2294593d9 | ||
|
|
3bcf3a4af5 | ||
|
|
03890b8a4c | ||
|
|
6be46bff28 | ||
|
|
831c17726f | ||
|
|
3cc26df6a1 | ||
|
|
985c744198 | ||
|
|
2dbec019af | ||
|
|
2fa95a281e | ||
|
|
ea6f3d1579 | ||
|
|
f9b0abdd14 | ||
|
|
10cda21401 |
8
images/VisualStudio.svg
Normal file
8
images/VisualStudio.svg
Normal file
File diff suppressed because one or more lines are too long
1
images/vscode.svg
Normal file
1
images/vscode.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="15" height="15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><defs><clipPath id="clip0"><rect x="479" y="279" width="15" height="15"/></clipPath><clipPath id="clip1"><rect x="-0.287396" y="-0.171573" width="152381" height="152381"/></clipPath><image width="35" height="35" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAMAAAApB0NrAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAH4UExURQAAAASExCGs7CC//wB9vQB5uxSU1yaz8ySy9AB8uwB8vAB5uxOR1Say8yax8iaw8gB6vAB6uwB7uwB4uROQ1Cax8ySy8QCAvwB3ugB5ugB2uROP1CWw8yav8wCT2QCQ1QCP1wB2uQB1uBOO0yWv8yat8wCP1QCP1QCP1QCO1QCQ1wB1tQB2uAB3uAB1tx+k6SSt8ySt8QCN0wCO1ACM0wBzuQBztAB1twB0tgB0tiSr8SSs8gCM1ACM1ACEywBwtQBxtAB0tgBztQB0sySp8SSr8gCL0wCK0gCL0wCHzwByuABusgBztQBttiOq8gB6wQCI0QCJ0gB7wySn8SOo8gBxtABtsABwtgCFzwCI0gCG0QCJ0SKn8SKn8iKl8QBvsABvsgBvsQBtsABssgCCzACG0QCF0ACDzyKm8QBtrwBusABsrgCAzQCE0ACF0ACF0ACE0CKj8SKl8QBssQBtrwBtrwBsrgBsrwCK1QCBzwCD0ACA0B2d7CGj8QBsrABrrQBwrwCA3wCC0ACCzwCBzwB+zhGM3iGi8SKh8QCAzQCAzgCAzwB9zRCK3iCh8SGh8SCf8QB+zAB/zgB8zRCJ3SCg8SCf7wB+zQB6zBCI3CCe8CCf8CCe8CCf8SCf/wB7zAB4yxGJ3h6c8CCd7yCf7wmA0RqX60C//5CaUeMAAACodFJOUwA8XAho+//ncID/////53iM//////9wCKv/////gCiYILf///+AMPP/70wYw/+3lP+AXP+MKNv/+3uA/3z/+////+NAgP9c9/////+7HP+A///MgP9Y+////7scgP+AeP/////7/+NA/2D/iyTb//t4gP808//vUBjD/7eU/yifIAi3//////+Ar////////4CI/////3B//////+9/CGj7/+dwEDhYBCm1XqwAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAFpSURBVDhPY2AYBaiAkYkZXQgdsLCysXPgV8XJxc3Dy8vHj0eVgKCQsIioqKioGLoMDIhLSEpKSknLyMjIyKLLyckrgJUoCgsLCyspq6ioqKiiKVFT19DUYmDQ1tEFAT19AwMDA0M0NUbGxsbGJqZm5ubm5haWDFbW1tbWVmhqGGxsbW1t7ewdHB2dnBkYXFxdXV1d0NUwuLl7eHh4enn7+DIwMLj4+fn5Yaph8A8IDAwMDBIHsYNDQkJCgtFVMISGhUdERkZGRkUzMDDExMbGxsahK4lPSExKTklNTU1NS2dgiMvIyMhAV5OZlZWVlZ2Tm5eXl5dfwFBYVFRUVIimpriktKycgaGisgoEqmtqa2tr0dUw1NU3gKjGpuaWlpbWtvb29vYOdDUw0NjZ1dXd09vX39c3AV0OASZOmjR5ytSpU6dOQ5dBAtN7ZsycNXvO3HnoEshg/oKFixYvQRdFA0uXLUcXGqEAAH4FV0z+qQbjAAAAAElFTkSuQmCC" preserveAspectRatio="none" id="img2"></image><clipPath id="clip3"><path d="M44291.4 46947.4 187148 46947.4 187148 188823 44291.4 188823Z" fill-rule="evenodd" clip-rule="evenodd"/></clipPath></defs><g clip-path="url(#clip0)" transform="translate(-479 -279)"><g clip-path="url(#clip1)" transform="matrix(0.000105 0 0 0.000105 479 279)"><g clip-path="url(#clip3)" transform="matrix(1 0 0 1.00692 -44291.4 -47272.4)"><use width="100%" height="100%" xlink:href="#img2" transform="scale(6709.45 6709.45)"></use></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -211,3 +211,12 @@ a:focus {
|
|||||||
.fileImportImg img {
|
.fileImportImg img {
|
||||||
filter: brightness(0) saturate(100%);
|
filter: brightness(0) saturate(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabPanesContainer {
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-container {
|
||||||
|
min-height: 500px;
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export interface IndexingPolicy {
|
|||||||
export interface VectorIndex {
|
export interface VectorIndex {
|
||||||
path: string;
|
path: string;
|
||||||
type: "flat" | "diskANN" | "quantizedFlat";
|
type: "flat" | "diskANN" | "quantizedFlat";
|
||||||
diskANNShardKey?: string;
|
vectorIndexShardKey?: string[];
|
||||||
indexingSearchListSize?: number;
|
indexingSearchListSize?: number;
|
||||||
quantizationByteSize?: number;
|
quantizationByteSize?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { AuthType } from "AuthType";
|
import { AuthType } from "AuthType";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { Features } from "Platform/Hosted/extractFeatures";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
@@ -253,7 +252,7 @@ describe("SettingsComponent", () => {
|
|||||||
it("should save throughput bucket changes when Save button is clicked", async () => {
|
it("should save throughput bucket changes when Save button is clicked", async () => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
apiType: "SQL",
|
apiType: "SQL",
|
||||||
features: { enableThroughputBuckets: true } as Features,
|
throughputBucketsEnabled: true,
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -191,10 +191,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||||
|
|
||||||
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
||||||
this.throughputBucketsEnabled =
|
this.throughputBucketsEnabled = userContext.throughputBucketsEnabled;
|
||||||
userContext.apiType === "SQL" &&
|
|
||||||
userContext.features.enableThroughputBuckets &&
|
|
||||||
userContext.authType === AuthType.AAD;
|
|
||||||
|
|
||||||
// Mongo container with system partition key still treat as "Fixed"
|
// Mongo container with system partition key still treat as "Fixed"
|
||||||
this.isFixedContainer =
|
this.isFixedContainer =
|
||||||
@@ -1074,11 +1071,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
databaseId: this.collection.databaseId,
|
databaseId: this.collection.databaseId,
|
||||||
collectionId: this.collection.id(),
|
collectionId: this.collection.id(),
|
||||||
currentOffer: this.collection.offer(),
|
currentOffer: this.collection.offer(),
|
||||||
autopilotThroughput: this.collection.offer().autoscaleMaxThroughput
|
autopilotThroughput: this.collection.offer?.()?.autoscaleMaxThroughput
|
||||||
? this.collection.offer().autoscaleMaxThroughput
|
? this.collection.offer?.()?.autoscaleMaxThroughput
|
||||||
: undefined,
|
: undefined,
|
||||||
manualThroughput: this.collection.offer().manualThroughput
|
manualThroughput: this.collection.offer?.()?.manualThroughput
|
||||||
? this.collection.offer().manualThroughput
|
? this.collection.offer?.()?.manualThroughput
|
||||||
: undefined,
|
: undefined,
|
||||||
throughputBuckets: this.state.throughputBuckets,
|
throughputBuckets: this.state.throughputBuckets,
|
||||||
});
|
});
|
||||||
@@ -1215,6 +1212,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isFullTextSearchEnabled: this.isFullTextSearchEnabled,
|
isFullTextSearchEnabled: this.isFullTextSearchEnabled,
|
||||||
shouldDiscardContainerPolicies: this.state.shouldDiscardContainerPolicies,
|
shouldDiscardContainerPolicies: this.state.shouldDiscardContainerPolicies,
|
||||||
resetShouldDiscardContainerPolicyChange: this.resetShouldDiscardContainerPolicies,
|
resetShouldDiscardContainerPolicyChange: this.resetShouldDiscardContainerPolicies,
|
||||||
|
isGlobalSecondaryIndex: this.isGlobalSecondaryIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
const indexingPolicyComponentProps: IndexingPolicyComponentProps = {
|
const indexingPolicyComponentProps: IndexingPolicyComponentProps = {
|
||||||
@@ -1343,7 +1341,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.throughputBucketsEnabled) {
|
if (this.throughputBucketsEnabled && !hasDatabaseSharedThroughput(this.collection) && this.offer) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.ThroughputBucketsTab,
|
tab: SettingsV2TabTypes.ThroughputBucketsTab,
|
||||||
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,
|
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface ContainerPolicyComponentProps {
|
|||||||
isFullTextSearchEnabled: boolean;
|
isFullTextSearchEnabled: boolean;
|
||||||
shouldDiscardContainerPolicies: boolean;
|
shouldDiscardContainerPolicies: boolean;
|
||||||
resetShouldDiscardContainerPolicyChange: () => void;
|
resetShouldDiscardContainerPolicyChange: () => void;
|
||||||
|
isGlobalSecondaryIndex?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> = ({
|
export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> = ({
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
|
|
||||||
it("renders the correct number of buckets", () => {
|
it("renders the correct number of buckets", () => {
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} />);
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
|
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders buckets in the correct order even if input is unordered", () => {
|
it("renders buckets in the correct order even if input is unordered", () => {
|
||||||
@@ -36,8 +36,14 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
];
|
];
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />);
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />);
|
||||||
|
|
||||||
const bucketLabels = screen.getAllByText(/Group \d+/).map((el) => el.textContent);
|
const bucketLabels = screen.getAllByText(/Bucket \d+/).map((el) => el.textContent);
|
||||||
expect(bucketLabels).toEqual(["Group 1 (Data Explorer Query Bucket)", "Group 2", "Group 3", "Group 4", "Group 5"]);
|
expect(bucketLabels).toEqual([
|
||||||
|
"Bucket 1 (Data Explorer Query Bucket)",
|
||||||
|
"Bucket 2",
|
||||||
|
"Bucket 3",
|
||||||
|
"Bucket 4",
|
||||||
|
"Bucket 5",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders all provided buckets even if they exceed the max default bucket count", () => {
|
it("renders all provided buckets even if they exceed the max default bucket count", () => {
|
||||||
@@ -53,7 +59,7 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
|
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />);
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />);
|
||||||
|
|
||||||
expect(screen.getAllByText(/Group \d+/)).toHaveLength(7);
|
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(7);
|
||||||
|
|
||||||
expect(screen.getByDisplayValue("50")).toBeInTheDocument();
|
expect(screen.getByDisplayValue("50")).toBeInTheDocument();
|
||||||
expect(screen.getByDisplayValue("60")).toBeInTheDocument();
|
expect(screen.getByDisplayValue("60")).toBeInTheDocument();
|
||||||
@@ -171,7 +177,7 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
|
|
||||||
it("ensures default buckets are used when no buckets are provided", () => {
|
it("ensures default buckets are used when no buckets are provided", () => {
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />);
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />);
|
||||||
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
|
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5);
|
||||||
expect(screen.getAllByDisplayValue("100")).toHaveLength(5);
|
expect(screen.getAllByDisplayValue("100")).toHaveLength(5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
value={bucket.maxThroughputPercentage}
|
value={bucket.maxThroughputPercentage}
|
||||||
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
||||||
showValue={false}
|
showValue={false}
|
||||||
label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
||||||
styles={{ root: { flex: 2, maxWidth: 400 } }}
|
styles={{ root: { flex: 2, maxWidth: 400 } }}
|
||||||
disabled={bucket.maxThroughputPercentage === 100}
|
disabled={bucket.maxThroughputPercentage === 100}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
serverId,
|
serverId,
|
||||||
numberOfRegions,
|
numberOfRegions,
|
||||||
isMultimaster,
|
isMultimaster,
|
||||||
true,
|
false,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
|
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||||
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
|
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
|
||||||
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import {
|
import {
|
||||||
@@ -29,6 +30,7 @@ export interface IVectorEmbeddingPoliciesComponentProps {
|
|||||||
discardChanges?: boolean;
|
discardChanges?: boolean;
|
||||||
onChangesDiscarded?: () => void;
|
onChangesDiscarded?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
isGlobalSecondaryIndex?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VectorEmbeddingPolicyData {
|
export interface VectorEmbeddingPolicyData {
|
||||||
@@ -39,8 +41,7 @@ export interface VectorEmbeddingPolicyData {
|
|||||||
indexType: VectorIndex["type"] | "none";
|
indexType: VectorIndex["type"] | "none";
|
||||||
pathError: string;
|
pathError: string;
|
||||||
dimensionsError: string;
|
dimensionsError: string;
|
||||||
diskANNShardKey?: string;
|
vectorIndexShardKey?: string[];
|
||||||
diskANNShardKeyError?: string;
|
|
||||||
indexingSearchListSize?: number;
|
indexingSearchListSize?: number;
|
||||||
indexingSearchListSizeError?: string;
|
indexingSearchListSizeError?: string;
|
||||||
quantizationByteSize?: number;
|
quantizationByteSize?: number;
|
||||||
@@ -87,6 +88,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
discardChanges,
|
discardChanges,
|
||||||
onChangesDiscarded,
|
onChangesDiscarded,
|
||||||
disabled,
|
disabled,
|
||||||
|
isGlobalSecondaryIndex,
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
||||||
let error = "";
|
let error = "";
|
||||||
@@ -132,12 +134,6 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: no restrictions yet due to this field being removed for now.
|
|
||||||
// Uncomment and replace with validation code when field is reinstated
|
|
||||||
// const onDiskANNShardKeyError = (shardKey: string): string => {
|
|
||||||
// return "";
|
|
||||||
// };
|
|
||||||
|
|
||||||
const initializeData = (vectorEmbeddings: VectorEmbedding[], vectorIndexes: VectorIndex[]) => {
|
const initializeData = (vectorEmbeddings: VectorEmbedding[], vectorIndexes: VectorIndex[]) => {
|
||||||
const mergedData: VectorEmbeddingPolicyData[] = [];
|
const mergedData: VectorEmbeddingPolicyData[] = [];
|
||||||
vectorEmbeddings.forEach((embedding) => {
|
vectorEmbeddings.forEach((embedding) => {
|
||||||
@@ -147,6 +143,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
indexType: matchingIndex?.type || "none",
|
indexType: matchingIndex?.type || "none",
|
||||||
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
|
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
|
||||||
quantizationByteSize: matchingIndex?.quantizationByteSize || undefined,
|
quantizationByteSize: matchingIndex?.quantizationByteSize || 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"),
|
||||||
});
|
});
|
||||||
@@ -186,6 +183,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
type: policy.indexType,
|
type: policy.indexType,
|
||||||
indexingSearchListSize: policy.indexingSearchListSize,
|
indexingSearchListSize: policy.indexingSearchListSize,
|
||||||
quantizationByteSize: policy.quantizationByteSize,
|
quantizationByteSize: policy.quantizationByteSize,
|
||||||
|
vectorIndexShardKey: policy.vectorIndexShardKey,
|
||||||
}) as VectorIndex,
|
}) as VectorIndex,
|
||||||
);
|
);
|
||||||
const validationPassed = vectorEmbeddingPolicyData.every(
|
const validationPassed = vectorEmbeddingPolicyData.every(
|
||||||
@@ -247,20 +245,16 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: uncomment after Ignite
|
const onShardKeyChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
// DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
|
const value = event.target.value.trim();
|
||||||
// const onDiskANNShardKeyChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
// const value = event.target.value.trim();
|
if (!vectorEmbeddings[index]?.vectorIndexShardKey?.[0] && !value.startsWith("/")) {
|
||||||
// const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
vectorEmbeddings[index].vectorIndexShardKey = ["/" + value];
|
||||||
// if (!vectorEmbeddings[index]?.diskANNShardKey && !value.startsWith("/")) {
|
} else {
|
||||||
// vectorEmbeddings[index].diskANNShardKey = "/" + value;
|
vectorEmbeddings[index].vectorIndexShardKey = [value];
|
||||||
// } else {
|
}
|
||||||
// vectorEmbeddings[index].diskANNShardKey = value;
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
// }
|
};
|
||||||
// const error = onDiskANNShardKeyError(value);
|
|
||||||
// vectorEmbeddings[index].diskANNShardKeyError = error;
|
|
||||||
// setVectorEmbeddingPolicyData(vectorEmbeddings);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const onVectorEmbeddingPolicyChange = (
|
const onVectorEmbeddingPolicyChange = (
|
||||||
index: number,
|
index: number,
|
||||||
@@ -292,6 +286,11 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 4 }}>
|
<Stack tokens={{ childrenGap: 4 }}>
|
||||||
{vectorEmbeddingPolicyData &&
|
{vectorEmbeddingPolicyData &&
|
||||||
@@ -402,6 +401,7 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
styles={labelStyles}
|
styles={labelStyles}
|
||||||
>
|
>
|
||||||
Quantization byte size
|
Quantization byte size
|
||||||
|
<InfoTooltip>{getQuantizationByteSizeTooltipContent()}</InfoTooltip>
|
||||||
</Label>
|
</Label>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={
|
disabled={
|
||||||
@@ -431,26 +431,18 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
{/*TODO: uncomment after Ignite */}
|
<Stack style={{ marginLeft: "10px" }}>
|
||||||
{/* DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
|
<Label disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"} styles={labelStyles}>
|
||||||
<Stack
|
Vector index shard key
|
||||||
style={{ marginLeft: "10px" }}
|
</Label>
|
||||||
>
|
|
||||||
<Label
|
|
||||||
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
|
||||||
styles={labelStyles}
|
|
||||||
>DiskANN shard key</Label>
|
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
||||||
id={`vector-policy-diskANNShardKey-${index + 1}`}
|
id={`vector-policy-vectorIndexShardKey-${index + 1}`}
|
||||||
styles={textFieldStyles}
|
styles={textFieldStyles}
|
||||||
value={String(vectorEmbeddingPolicy.diskANNShardKey || "")}
|
value={String(vectorEmbeddingPolicy.vectorIndexShardKey?.[0] ?? "")}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onShardKeyChange(index, event)}
|
||||||
onDiskANNShardKeyChange(index, event)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
*/}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } fro
|
|||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
|
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
|
||||||
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
@@ -282,6 +283,42 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openInVsCode(): void {
|
||||||
|
const activeTab = useTabs.getState().activeTab;
|
||||||
|
const resourceId = encodeURIComponent(userContext.databaseAccount.id);
|
||||||
|
const database = encodeURIComponent(activeTab?.collection?.databaseId);
|
||||||
|
const container = encodeURIComponent(activeTab?.collection?.id());
|
||||||
|
const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
|
||||||
|
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
|
||||||
|
|
||||||
|
const openVSCodeDialogProps: DialogProps = {
|
||||||
|
linkProps: {
|
||||||
|
linkText: "Download Visual Studio Code",
|
||||||
|
linkUrl: "https://code.visualstudio.com/download",
|
||||||
|
},
|
||||||
|
isModal: true,
|
||||||
|
title: `Open your Azure Cosmos DB account in Visual Studio Code`,
|
||||||
|
subText: `Please ensure Visual Studio Code is installed on your device.
|
||||||
|
If you don't have it installed, please download it from the link below.`,
|
||||||
|
primaryButtonText: "Open in VS Code",
|
||||||
|
secondaryButtonText: "Cancel",
|
||||||
|
|
||||||
|
onPrimaryButtonClick: () => {
|
||||||
|
try {
|
||||||
|
window.location.href = vscodeUrl;
|
||||||
|
TelemetryProcessor.traceStart(Action.OpenVSCode);
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleError(`Failed to open VS Code: ${getErrorMessage(error)}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSecondaryButtonClick: () => {
|
||||||
|
useDialog.getState().closeDialog();
|
||||||
|
TelemetryProcessor.traceCancel(Action.OpenVSCode);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
useDialog.getState().openDialog(openVSCodeDialogProps);
|
||||||
|
}
|
||||||
|
|
||||||
public async openCESCVAFeedbackBlade(): Promise<void> {
|
public async openCESCVAFeedbackBlade(): Promise<void> {
|
||||||
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
|
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
|
||||||
Logger.logInfo(
|
Logger.logInfo(
|
||||||
@@ -1133,6 +1170,11 @@ export default class Explorer {
|
|||||||
await this.initNotebooks(userContext.databaseAccount);
|
await this.initNotebooks(userContext.databaseAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL") {
|
||||||
|
const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing");
|
||||||
|
updateUserContext({ throughputBucketsEnabled });
|
||||||
|
}
|
||||||
|
|
||||||
this.refreshSampleData();
|
this.refreshSampleData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
|
|||||||
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
|
||||||
import SettingsIcon from "../../../../images/settings_15x15.svg";
|
import SettingsIcon from "../../../../images/settings_15x15.svg";
|
||||||
import SynapseIcon from "../../../../images/synapse-link.svg";
|
import SynapseIcon from "../../../../images/synapse-link.svg";
|
||||||
|
import VSCodeIcon from "../../../../images/vscode.svg";
|
||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Platform, configContext } from "../../../ConfigContext";
|
import { Platform, configContext } from "../../../ConfigContext";
|
||||||
@@ -60,6 +61,10 @@ export function createStaticCommandBarButtons(
|
|||||||
addDivider();
|
addDivider();
|
||||||
buttons.push(addSynapseLink);
|
buttons.push(addSynapseLink);
|
||||||
}
|
}
|
||||||
|
if (userContext.apiType !== "Gremlin") {
|
||||||
|
const addVsCode = createOpenVsCodeDialogButton(container);
|
||||||
|
buttons.push(addVsCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDataplaneRbacSupported(userContext.apiType)) {
|
if (isDataplaneRbacSupported(userContext.apiType)) {
|
||||||
@@ -268,6 +273,18 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps {
|
||||||
|
const label = "Visual Studio Code";
|
||||||
|
return {
|
||||||
|
iconSrc: VSCodeIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: () => container.openInVsCode(),
|
||||||
|
commandButtonLabel: label,
|
||||||
|
hasPopup: false,
|
||||||
|
ariaLabel: label,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps {
|
function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps {
|
||||||
if (configContext.platform !== Platform.Portal) {
|
if (configContext.platform !== Platform.Portal) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -501,5 +518,6 @@ export function createPostgreButtons(container: Explorer): CommandButtonComponen
|
|||||||
export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] {
|
export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] {
|
||||||
const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo);
|
const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo);
|
||||||
|
|
||||||
return [openVCoreMongoTerminalButton];
|
const addVsCode = createOpenVsCodeDialogButton(container);
|
||||||
|
return [openVCoreMongoTerminalButton, addVsCode];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,11 +175,6 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalSecondaryIndexThroughput > CollectionCreation.MaxRUPerPartition) {
|
|
||||||
setErrorMessage("Unsharded collections support up to 10,000 RUs");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showVectorSearchParameters()) {
|
if (showVectorSearchParameters()) {
|
||||||
if (!vectorPolicyValidated) {
|
if (!vectorPolicyValidated) {
|
||||||
setErrorMessage("Please fix errors in container vector policy");
|
setErrorMessage("Please fix errors in container vector policy");
|
||||||
@@ -388,6 +383,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
|||||||
setVectorIndexingPolicy,
|
setVectorIndexingPolicy,
|
||||||
vectorPolicyValidated,
|
vectorPolicyValidated,
|
||||||
setVectorPolicyValidated,
|
setVectorPolicyValidated,
|
||||||
|
isGlobalSecondaryIndex: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const ThroughputComponent = (props: ThroughputComponentProps): JSX.Elemen
|
|||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !useDatabases.getState().isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={false}
|
isSharded={true}
|
||||||
isFreeTier={isFreeTierAccount()}
|
isFreeTier={isFreeTierAccount()}
|
||||||
isQuickstart={false}
|
isQuickstart={false}
|
||||||
isGlobalSecondaryIndex={true}
|
isGlobalSecondaryIndex={true}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export interface VectorSearchComponentProps {
|
|||||||
vectorIndexingPolicy: VectorIndex[];
|
vectorIndexingPolicy: VectorIndex[];
|
||||||
setVectorIndexingPolicy: React.Dispatch<React.SetStateAction<VectorIndex[]>>;
|
setVectorIndexingPolicy: React.Dispatch<React.SetStateAction<VectorIndex[]>>;
|
||||||
setVectorPolicyValidated: React.Dispatch<React.SetStateAction<boolean>>;
|
setVectorPolicyValidated: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
isGlobalSecondaryIndex?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.Element => {
|
export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.Element => {
|
||||||
@@ -23,6 +24,7 @@ export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.El
|
|||||||
vectorIndexingPolicy,
|
vectorIndexingPolicy,
|
||||||
setVectorIndexingPolicy,
|
setVectorIndexingPolicy,
|
||||||
setVectorPolicyValidated,
|
setVectorPolicyValidated,
|
||||||
|
isGlobalSecondaryIndex,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,6 +51,7 @@ export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.El
|
|||||||
setVectorIndexingPolicy(vectorIndexingPolicy);
|
setVectorIndexingPolicy(vectorIndexingPolicy);
|
||||||
setVectorPolicyValidated(vectorPolicyValidated);
|
setVectorPolicyValidated(vectorPolicyValidated);
|
||||||
}}
|
}}
|
||||||
|
isGlobalSecondaryIndex={isGlobalSecondaryIndex}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Accordion top class
|
* Accordion top class
|
||||||
*/
|
*/
|
||||||
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
import { makeStyles, tokens } from "@fluentui/react-components";
|
||||||
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
||||||
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
|
||||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||||
@@ -9,7 +9,6 @@ import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUt
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
|
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
|
||||||
import LinkIcon from "../../../images/Link_blue.svg";
|
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
|
||||||
export interface SplashScreenProps {
|
export interface SplashScreenProps {
|
||||||
@@ -186,12 +185,12 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
|
|||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
{getSplashScreenButtons()}
|
{getSplashScreenButtons()}
|
||||||
<div className={styles.footer}>
|
{/* <div className={styles.footer}>
|
||||||
Need help?{" "}
|
Need help?{" "}
|
||||||
<Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank">
|
<Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank">
|
||||||
Learn more <img src={LinkIcon} alt="Learn more" />
|
Learn more <img src={LinkIcon} alt="Learn more" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div> */}
|
||||||
</CosmosFluentProvider>
|
</CosmosFluentProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import LinkIcon from "../../../images/Link_blue.svg";
|
|||||||
import PowerShellIcon from "../../../images/PowerShell.svg";
|
import PowerShellIcon from "../../../images/PowerShell.svg";
|
||||||
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
|
||||||
import QuickStartIcon from "../../../images/Quickstart_Lightning.svg";
|
import QuickStartIcon from "../../../images/Quickstart_Lightning.svg";
|
||||||
|
import VisualStudioIcon from "../../../images/VisualStudio.svg";
|
||||||
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
|
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
|
||||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
@@ -458,10 +459,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userContext.apiType === "VCoreMongo") {
|
if (userContext.apiType === "VCoreMongo") {
|
||||||
icon = ContainersIcon;
|
icon = VisualStudioIcon;
|
||||||
title = "Connect with Studio 3T";
|
title = "Connect with VS Code";
|
||||||
description = "Prefer Studio 3T? Find your connection strings here";
|
description = "Query and Manage your MongoDB cluster in Visual Studio Code";
|
||||||
onClick = () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect);
|
onClick = () => this.container.openInVsCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -43,32 +43,52 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
|
|||||||
await ensureCloudShellProviderRegistered();
|
await ensureCloudShellProviderRegistered();
|
||||||
|
|
||||||
resolvedRegion = determineCloudShellRegion();
|
resolvedRegion = determineCloudShellRegion();
|
||||||
// Ask for user consent for region
|
|
||||||
const consentGranted = await askConfirmation(
|
resolvedRegion = determineCloudShellRegion();
|
||||||
terminal,
|
|
||||||
formatWarningMessage(
|
terminal.writeln(formatWarningMessage("⚠️ IMPORTANT: Azure Cloud Shell Region Notice ⚠️"));
|
||||||
"The shell environment may be operating in a region different from that of the database, which could impact performance or data compliance. Do you wish to proceed?",
|
terminal.writeln(
|
||||||
|
formatInfoMessage(
|
||||||
|
"The Cloud Shell environment will operate in a region that may differ from your database's region.",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
terminal.writeln(formatInfoMessage("This has two potential implications:"));
|
||||||
|
terminal.writeln(formatInfoMessage("1. Performance Impact:"));
|
||||||
|
terminal.writeln(
|
||||||
|
formatInfoMessage(" Commands may experience higher latency due to geographic distance between regions."),
|
||||||
|
);
|
||||||
|
terminal.writeln(formatInfoMessage("2. Data Compliance Considerations:"));
|
||||||
|
terminal.writeln(
|
||||||
|
formatInfoMessage(
|
||||||
|
" Data processed through this shell could temporarily reside in a different geographic region,",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
terminal.writeln(
|
||||||
|
formatInfoMessage(" which may affect compliance with data residency requirements or regulations specific"),
|
||||||
|
);
|
||||||
|
terminal.writeln(formatInfoMessage(" to your organization."));
|
||||||
|
terminal.writeln("");
|
||||||
|
|
||||||
|
terminal.writeln("\x1b[94mFor more information on Azure Cosmos DB data governance and compliance, please visit:");
|
||||||
|
terminal.writeln("\x1b[94mhttps://learn.microsoft.com/en-us/azure/cosmos-db/data-residency\x1b[0m");
|
||||||
|
|
||||||
|
// Ask for user consent for region
|
||||||
|
const consentGranted = await askConfirmation(terminal, formatWarningMessage("Do you wish to proceed?"));
|
||||||
|
|
||||||
// Track user decision
|
// Track user decision
|
||||||
TelemetryProcessor.trace(
|
TelemetryProcessor.trace(
|
||||||
Action.CloudShellUserConsent,
|
Action.CloudShellUserConsent,
|
||||||
consentGranted ? ActionModifiers.Success : ActionModifiers.Cancel,
|
consentGranted ? ActionModifiers.Success : ActionModifiers.Cancel,
|
||||||
{ dataExplorerArea: Areas.CloudShell },
|
{
|
||||||
|
dataExplorerArea: Areas.CloudShell,
|
||||||
|
shellType: TerminalKind[shellType],
|
||||||
|
isConsent: consentGranted,
|
||||||
|
region: resolvedRegion,
|
||||||
|
},
|
||||||
|
startKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!consentGranted) {
|
if (!consentGranted) {
|
||||||
TelemetryProcessor.traceCancel(
|
|
||||||
Action.CloudShellTerminalSession,
|
|
||||||
{
|
|
||||||
shellType: TerminalKind[shellType],
|
|
||||||
dataExplorerArea: Areas.CloudShell,
|
|
||||||
region: resolvedRegion,
|
|
||||||
isConsent: false,
|
|
||||||
},
|
|
||||||
startKey,
|
|
||||||
);
|
|
||||||
terminal.writeln(
|
terminal.writeln(
|
||||||
formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."),
|
formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."),
|
||||||
);
|
);
|
||||||
@@ -262,28 +282,27 @@ export const configureSocketConnection = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sendTerminalStartupCommands = (socket: WebSocket, initCommands: string): void => {
|
export const sendTerminalStartupCommands = (socket: WebSocket, initCommands: string): void => {
|
||||||
|
// ensures connections don't remain open indefinitely by implementing an automatic timeout after 120 minutes.
|
||||||
|
const keepSocketAlive = (socket: WebSocket) => {
|
||||||
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
|
if (pingCount >= MAX_PING_COUNT) {
|
||||||
|
socket.close();
|
||||||
|
} else {
|
||||||
|
pingCount++;
|
||||||
|
// The code uses a recursive setTimeout pattern rather than setInterval,
|
||||||
|
// which ensures each new ping only happens after the previous one completes
|
||||||
|
// and naturally stops if the socket closes.
|
||||||
|
keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
socket.send(initCommands);
|
socket.send(initCommands);
|
||||||
|
keepSocketAlive(socket);
|
||||||
} else {
|
} else {
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
socket.send(initCommands);
|
socket.send(initCommands);
|
||||||
|
|
||||||
// ensures connections don't remain open indefinitely by implementing an automatic timeout after 20 minutes.
|
|
||||||
const keepSocketAlive = (socket: WebSocket) => {
|
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
|
||||||
if (pingCount >= MAX_PING_COUNT) {
|
|
||||||
socket.close();
|
|
||||||
} else {
|
|
||||||
socket.send("");
|
|
||||||
pingCount++;
|
|
||||||
// The code uses a recursive setTimeout pattern rather than setInterval,
|
|
||||||
// which ensures each new ping only happens after the previous one completes
|
|
||||||
// and naturally stops if the socket closes.
|
|
||||||
keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
keepSocketAlive(socket);
|
keepSocketAlive(socket);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close thi
|
|||||||
* the required methods.
|
* the required methods.
|
||||||
*/
|
*/
|
||||||
export abstract class AbstractShellHandler {
|
export abstract class AbstractShellHandler {
|
||||||
|
/**
|
||||||
|
* The name of the application using this shell handler.
|
||||||
|
* This is used for telemetry and logging purposes.
|
||||||
|
*/
|
||||||
|
protected APP_NAME = "CosmosExplorerTerminal";
|
||||||
|
|
||||||
abstract getShellName(): string;
|
abstract getShellName(): string;
|
||||||
abstract getSetUpCommands(): string[];
|
abstract getSetUpCommands(): string[];
|
||||||
abstract getConnectionCommand(): string;
|
abstract getConnectionCommand(): string;
|
||||||
@@ -56,4 +62,30 @@ export abstract class AbstractShellHandler {
|
|||||||
|
|
||||||
return allCommands.join("\n").concat("\n");
|
return allCommands.join("\n").concat("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup commands for MongoDB shell:
|
||||||
|
*
|
||||||
|
* 1. Check if mongosh is already installed
|
||||||
|
* 2. Download mongosh package if not installed
|
||||||
|
* 3. Extract the package to access mongosh binaries
|
||||||
|
* 4. Move extracted files to ~/mongosh directory
|
||||||
|
* 5. Add mongosh binary path to system PATH
|
||||||
|
* 6. Apply PATH changes by sourcing .bashrc
|
||||||
|
*
|
||||||
|
* Each command runs conditionally only if mongosh
|
||||||
|
* is not already present in the environment.
|
||||||
|
*/
|
||||||
|
protected mongoShellSetupCommands(): string[] {
|
||||||
|
const PACKAGE_VERSION: string = "2.5.0";
|
||||||
|
return [
|
||||||
|
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
||||||
|
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||||
|
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||||
|
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh/bin && mv mongosh-${PACKAGE_VERSION}-linux-x64/bin/mongosh ~/mongosh/bin/ && chmod +x ~/mongosh/bin/mongosh; fi`,
|
||||||
|
`if ! command -v mongosh &> /dev/null; then rm -rf mongosh-${PACKAGE_VERSION}-linux-x64 mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
||||||
|
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
|
||||||
|
"source ~/.bashrc",
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe("CassandraShellHandler", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should return correct connection command", () => {
|
test("should return correct connection command", () => {
|
||||||
const expectedCommand = "cqlsh test-endpoint.cassandra.cosmos.azure.com 10350 -u test-account -p test-key --ssl";
|
const expectedCommand = `cqlsh test-endpoint.cassandra.cosmos.azure.com 10350 -u test-account -p test-key --ssl`;
|
||||||
|
|
||||||
expect(handler.getConnectionCommand()).toBe(expectedCommand);
|
expect(handler.getConnectionCommand()).toBe(expectedCommand);
|
||||||
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-endpoint.cassandra.cosmos.azure.com:443/");
|
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-endpoint.cassandra.cosmos.azure.com:443/");
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe("MongoShellHandler", () => {
|
|||||||
const commands = mongoShellHandler.getSetUpCommands();
|
const commands = mongoShellHandler.getSetUpCommands();
|
||||||
|
|
||||||
expect(Array.isArray(commands)).toBe(true);
|
expect(Array.isArray(commands)).toBe(true);
|
||||||
expect(commands.length).toBe(6);
|
expect(commands.length).toBe(7);
|
||||||
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
|
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -91,7 +91,7 @@ describe("MongoShellHandler", () => {
|
|||||||
const command = mongoShellHandler.getConnectionCommand();
|
const command = mongoShellHandler.getConnectionCommand();
|
||||||
|
|
||||||
expect(command).toBe(
|
expect(command).toBe(
|
||||||
"mongosh --host test-mongo.documents.azure.com --port 10255 --username test-account --password test-key --tls --tlsAllowInvalidCertificates",
|
"mongosh mongodb://test-mongo.documents.azure.com:10255?appName=CosmosExplorerTerminal --username test-account --password test-key --tls --tlsAllowInvalidCertificates",
|
||||||
);
|
);
|
||||||
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/");
|
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/");
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { userContext } from "../../../../UserContext";
|
|||||||
import { getHostFromUrl } from "../Utils/CommonUtils";
|
import { getHostFromUrl } from "../Utils/CommonUtils";
|
||||||
import { AbstractShellHandler } from "./AbstractShellHandler";
|
import { AbstractShellHandler } from "./AbstractShellHandler";
|
||||||
|
|
||||||
const PACKAGE_VERSION: string = "2.5.0";
|
|
||||||
|
|
||||||
export class MongoShellHandler extends AbstractShellHandler {
|
export class MongoShellHandler extends AbstractShellHandler {
|
||||||
private _key: string;
|
private _key: string;
|
||||||
private _endpoint: string | undefined;
|
private _endpoint: string | undefined;
|
||||||
@@ -18,14 +16,7 @@ export class MongoShellHandler extends AbstractShellHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getSetUpCommands(): string[] {
|
public getSetUpCommands(): string[] {
|
||||||
return [
|
return this.mongoShellSetupCommands();
|
||||||
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
|
||||||
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
|
||||||
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
|
||||||
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh && mv mongosh-${PACKAGE_VERSION}-linux-x64/* ~/mongosh/; fi`,
|
|
||||||
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
|
|
||||||
"source ~/.bashrc",
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getConnectionCommand(): string {
|
public getConnectionCommand(): string {
|
||||||
@@ -37,9 +28,17 @@ export class MongoShellHandler extends AbstractShellHandler {
|
|||||||
if (!dbName) {
|
if (!dbName) {
|
||||||
return "echo 'Database name not found.'";
|
return "echo 'Database name not found.'";
|
||||||
}
|
}
|
||||||
return `mongosh --host ${getHostFromUrl(this._endpoint)} --port 10255 --username ${dbName} --password ${
|
return (
|
||||||
this._key
|
"mongosh mongodb://" +
|
||||||
} --tls --tlsAllowInvalidCertificates`;
|
getHostFromUrl(this._endpoint) +
|
||||||
|
":10255?appName=" +
|
||||||
|
this.APP_NAME +
|
||||||
|
" --username " +
|
||||||
|
dbName +
|
||||||
|
" --password " +
|
||||||
|
this._key +
|
||||||
|
" --tls --tlsAllowInvalidCertificates"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTerminalSuppressedData(): string {
|
public getTerminalSuppressedData(): string {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class PostgresShellHandler extends AbstractShellHandler {
|
|||||||
// All Azure Cosmos DB PostgreSQL deployments follow this convention.
|
// All Azure Cosmos DB PostgreSQL deployments follow this convention.
|
||||||
// Ref. https://learn.microsoft.com/en-us/azure/cosmos-db/postgresql/reference-limits#database-creation
|
// Ref. https://learn.microsoft.com/en-us/azure/cosmos-db/postgresql/reference-limits#database-creation
|
||||||
const loginName = userContext.postgresConnectionStrParams.adminLogin;
|
const loginName = userContext.postgresConnectionStrParams.adminLogin;
|
||||||
return `psql -h "${this._endpoint}" -p 5432 -d "citus" -U "${loginName}" --set=sslmode=require`;
|
return `psql -h "${this._endpoint}" -p 5432 -d "citus" -U "${loginName}" --set=sslmode=require --set=application_name=${this.APP_NAME}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTerminalSuppressedData(): string {
|
public getTerminalSuppressedData(): string {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe("VCoreMongoShellHandler", () => {
|
|||||||
const commands = vcoreMongoShellHandler.getSetUpCommands();
|
const commands = vcoreMongoShellHandler.getSetUpCommands();
|
||||||
|
|
||||||
expect(Array.isArray(commands)).toBe(true);
|
expect(Array.isArray(commands)).toBe(true);
|
||||||
expect(commands.length).toBe(6);
|
expect(commands.length).toBe(7);
|
||||||
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
|
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
|
||||||
expect(commands[0]).toContain("mongosh not found");
|
expect(commands[0]).toContain("mongosh not found");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
import { AbstractShellHandler } from "./AbstractShellHandler";
|
import { AbstractShellHandler } from "./AbstractShellHandler";
|
||||||
|
|
||||||
const PACKAGE_VERSION: string = "2.5.0";
|
|
||||||
|
|
||||||
export class VCoreMongoShellHandler extends AbstractShellHandler {
|
export class VCoreMongoShellHandler extends AbstractShellHandler {
|
||||||
private _endpoint: string | undefined;
|
private _endpoint: string | undefined;
|
||||||
|
|
||||||
@@ -15,28 +13,8 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
|
|||||||
return "MongoDB VCore";
|
return "MongoDB VCore";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup commands for MongoDB VCore shell:
|
|
||||||
*
|
|
||||||
* 1. Check if mongosh is already installed
|
|
||||||
* 2. Download mongosh package if not installed
|
|
||||||
* 3. Extract the package to access mongosh binaries
|
|
||||||
* 4. Move extracted files to ~/mongosh directory
|
|
||||||
* 5. Add mongosh binary path to system PATH
|
|
||||||
* 6. Apply PATH changes by sourcing .bashrc
|
|
||||||
*
|
|
||||||
* Each command runs conditionally only if mongosh
|
|
||||||
* is not already present in the environment.
|
|
||||||
*/
|
|
||||||
public getSetUpCommands(): string[] {
|
public getSetUpCommands(): string[] {
|
||||||
return [
|
return this.mongoShellSetupCommands();
|
||||||
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
|
|
||||||
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
|
||||||
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
|
|
||||||
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh && mv mongosh-${PACKAGE_VERSION}-linux-x64/* ~/mongosh/; fi`,
|
|
||||||
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
|
|
||||||
"source ~/.bashrc",
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getConnectionCommand(): string {
|
public getConnectionCommand(): string {
|
||||||
@@ -45,7 +23,7 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userName = userContext.vcoreMongoConnectionParams.adminLogin;
|
const userName = userContext.vcoreMongoConnectionParams.adminLogin;
|
||||||
return `mongosh "mongodb+srv://${userName}:@${this._endpoint}/?authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000"`;
|
return `mongosh "mongodb+srv://${userName}:@${this._endpoint}/?authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000&appName=${this.APP_NAME}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTerminalSuppressedData(): string {
|
public getTerminalSuppressedData(): string {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { AbstractShellHandler } from "Explorer/Tabs/CloudShellTab/ShellTypes/AbstractShellHandler";
|
|
||||||
import { IDisposable, ITerminalAddon, Terminal } from "@xterm/xterm";
|
import { IDisposable, ITerminalAddon, Terminal } from "@xterm/xterm";
|
||||||
|
import { AbstractShellHandler } from "../ShellTypes/AbstractShellHandler";
|
||||||
|
import { formatErrorMessage } from "./TerminalLogFormats";
|
||||||
|
|
||||||
interface IAttachOptions {
|
interface IAttachOptions {
|
||||||
bidirectional?: boolean;
|
bidirectional?: boolean;
|
||||||
@@ -56,8 +57,27 @@ export class AttachAddon implements ITerminalAddon {
|
|||||||
this._disposables.push(terminal.onBinary((data) => this._sendBinary(data)));
|
this._disposables.push(terminal.onBinary((data) => this._sendBinary(data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._disposables.push(addSocketListener(this._socket, "close", () => this.dispose()));
|
this._disposables.push(addSocketListener(this._socket, "close", () => this._handleSocketClose(terminal)));
|
||||||
this._disposables.push(addSocketListener(this._socket, "error", () => this.dispose()));
|
this._disposables.push(addSocketListener(this._socket, "error", () => this._handleSocketClose(terminal)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles socket close events by terminating processes and showing a message
|
||||||
|
*/
|
||||||
|
private _handleSocketClose(terminal: Terminal): void {
|
||||||
|
if (terminal) {
|
||||||
|
terminal.writeln(
|
||||||
|
formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send exit command to terminal
|
||||||
|
if (this._bidirectional) {
|
||||||
|
terminal.write(formatErrorMessage("exit\r\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up resources
|
||||||
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export enum Action {
|
|||||||
UploadDocuments, // Used in Fabric. Please do not rename.
|
UploadDocuments, // Used in Fabric. Please do not rename.
|
||||||
CloudShellUserConsent,
|
CloudShellUserConsent,
|
||||||
CloudShellTerminalSession,
|
CloudShellTerminalSession,
|
||||||
|
OpenVSCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionModifiers = {
|
export const ActionModifiers = {
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export interface UserContext {
|
|||||||
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
||||||
readonly dataPlaneRbacEnabled?: boolean;
|
readonly dataPlaneRbacEnabled?: boolean;
|
||||||
readonly refreshCosmosClient?: boolean;
|
readonly refreshCosmosClient?: boolean;
|
||||||
|
throughputBucketsEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
||||||
|
|||||||
48
src/Utils/FeatureRegistrationUtils.ts
Normal file
48
src/Utils/FeatureRegistrationUtils.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { configContext } from "ConfigContext";
|
||||||
|
import { FeatureRegistration } from "Contracts/DataModels";
|
||||||
|
import { AuthorizationTokenHeaderMetadata } from "Contracts/ViewModels";
|
||||||
|
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
||||||
|
|
||||||
|
export const featureRegistered = async (subscriptionId: string, feature: string) => {
|
||||||
|
const api_version = "2021-07-01";
|
||||||
|
const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/${feature}?api-version=${api_version}`;
|
||||||
|
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
|
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||||
|
|
||||||
|
let response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await _fetchWithTimeout(url, headers);
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response?.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const featureRegistration = (await response?.json()) as FeatureRegistration;
|
||||||
|
return featureRegistration?.properties?.state === "Registered";
|
||||||
|
};
|
||||||
|
|
||||||
|
async function _fetchWithTimeout(
|
||||||
|
url: string,
|
||||||
|
headers: {
|
||||||
|
[x: string]: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const timeout = 10000;
|
||||||
|
const options = { timeout };
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const id = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
|
const response = await window.fetch(url, {
|
||||||
|
headers,
|
||||||
|
...options,
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
clearTimeout(id);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -124,7 +124,7 @@ export const extractPartitionKeyValues = (
|
|||||||
documentContent: any,
|
documentContent: any,
|
||||||
partitionKeyDefinition: PartitionKeyDefinition,
|
partitionKeyDefinition: PartitionKeyDefinition,
|
||||||
): PartitionKey[] => {
|
): PartitionKey[] => {
|
||||||
if (!partitionKeyDefinition.paths || partitionKeyDefinition.paths.length === 0 || partitionKeyDefinition.systemKey) {
|
if (!partitionKeyDefinition.paths || partitionKeyDefinition.paths.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ export const extractPartitionKeyValues = (
|
|||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
partitionKeyValues.push(value);
|
partitionKeyValues.push(value);
|
||||||
} else {
|
} else if (!partitionKeyDefinition.systemKey) {
|
||||||
partitionKeyValues.push({});
|
partitionKeyValues.push({});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user