mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-07 11:36:47 +00:00
Compare commits
6 Commits
users/aisa
...
release/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2294593d9 | ||
|
|
3bcf3a4af5 | ||
|
|
03890b8a4c | ||
|
|
6be46bff28 | ||
|
|
831c17726f | ||
|
|
3cc26df6a1 |
8
images/VisualStudio.svg
Normal file
8
images/VisualStudio.svg
Normal file
File diff suppressed because one or more lines are too long
@@ -187,8 +187,7 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
PROXY_PATH: "/proxy",
|
PROXY_PATH: "/proxy",
|
||||||
EMULATOR_ENDPOINT: "https://localhost:8081",
|
EMULATOR_ENDPOINT: "https://localhost:8081",
|
||||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
|
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
|
||||||
// MONGO_PROXY_ENDPOINT: "https://cosmos-db-portal-mongoproxy1-mpac-westus.azurewebsites.net",
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
|
||||||
MONGO_PROXY_ENDPOINT: "https://localhost:7238",
|
|
||||||
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Mpac,
|
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Mpac,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
@@ -1344,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} />,
|
||||||
|
|||||||
@@ -143,39 +143,4 @@ describe("SubSettingsComponent", () => {
|
|||||||
expect(subSettingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn);
|
expect(subSettingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn);
|
||||||
expect(subSettingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff);
|
expect(subSettingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uniqueKey is visible", () => {
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableSQL" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
const subSettingsComponent = new SubSettingsComponent(baseProps);
|
|
||||||
expect(subSettingsComponent.getUniqueKeyVisible()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uniqueKey not visible due to no keys", () => {
|
|
||||||
const props = {
|
|
||||||
...baseProps,
|
|
||||||
...(baseProps.collection.rawDataModel.uniqueKeyPolicy.uniqueKeys = []),
|
|
||||||
};
|
|
||||||
const subSettingsComponent = new SubSettingsComponent(props);
|
|
||||||
expect(subSettingsComponent.getUniqueKeyVisible()).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uniqueKey not visible for API", () => {
|
|
||||||
const newContainer = new Explorer();
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableMongo" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
const props = { ...baseProps, container: newContainer };
|
|
||||||
const subSettingsComponent = new SubSettingsComponent(props);
|
|
||||||
expect(subSettingsComponent.getUniqueKeyVisible()).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,16 +63,12 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
private geospatialVisible: boolean;
|
private geospatialVisible: boolean;
|
||||||
private partitionKeyValue: string;
|
private partitionKeyValue: string;
|
||||||
private partitionKeyName: string;
|
private partitionKeyName: string;
|
||||||
private uniqueKeyName: string;
|
|
||||||
private uniqueKeyValue: string;
|
|
||||||
|
|
||||||
constructor(props: SubSettingsComponentProps) {
|
constructor(props: SubSettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.geospatialVisible = userContext.apiType === "SQL";
|
this.geospatialVisible = userContext.apiType === "SQL";
|
||||||
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
||||||
this.partitionKeyValue = this.getPartitionKeyValue();
|
this.partitionKeyValue = this.getPartitionKeyValue();
|
||||||
this.uniqueKeyName = "Unique keys";
|
|
||||||
this.uniqueKeyValue = this.getUniqueKeyValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
@@ -355,28 +351,6 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
public isLargePartitionKeyEnabled = (): boolean => this.props.collection.partitionKey?.version >= 2;
|
public isLargePartitionKeyEnabled = (): boolean => this.props.collection.partitionKey?.version >= 2;
|
||||||
public isHierarchicalPartitionedContainer = (): boolean => this.props.collection.partitionKey?.kind === "MultiHash";
|
public isHierarchicalPartitionedContainer = (): boolean => this.props.collection.partitionKey?.kind === "MultiHash";
|
||||||
|
|
||||||
public getUniqueKeyVisible = (): boolean => {
|
|
||||||
return this.props.collection.rawDataModel.uniqueKeyPolicy?.uniqueKeys.length > 0 && userContext.apiType === "SQL";
|
|
||||||
};
|
|
||||||
|
|
||||||
private getUniqueKeyValue = (): string => {
|
|
||||||
const paths = this.props.collection.rawDataModel.uniqueKeyPolicy?.uniqueKeys?.[0]?.paths;
|
|
||||||
return paths?.join(", ") || "";
|
|
||||||
};
|
|
||||||
|
|
||||||
private getUniqueKeyComponent = (): JSX.Element => (
|
|
||||||
<Stack {...titleAndInputStackProps}>
|
|
||||||
{this.getUniqueKeyVisible() && (
|
|
||||||
<TextField
|
|
||||||
label={this.uniqueKeyName}
|
|
||||||
disabled
|
|
||||||
styles={getTextFieldStyles(undefined, undefined)}
|
|
||||||
defaultValue={this.uniqueKeyValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...subComponentStackProps}>
|
<Stack {...subComponentStackProps}>
|
||||||
@@ -389,8 +363,6 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
{this.props.changeFeedPolicyVisible && this.getChangeFeedComponent()}
|
{this.props.changeFeedPolicyVisible && this.getChangeFeedComponent()}
|
||||||
|
|
||||||
{this.getPartitionKeyComponent()}
|
{this.getPartitionKeyComponent()}
|
||||||
|
|
||||||
{this.getUniqueKeyComponent()}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -231,34 +231,6 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
|||||||
Non-hierarchically partitioned container.
|
Non-hierarchically partitioned container.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
|
||||||
tokens={
|
|
||||||
{
|
|
||||||
"childrenGap": 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
defaultValue="/id"
|
|
||||||
disabled={true}
|
|
||||||
label="Unique keys"
|
|
||||||
styles={
|
|
||||||
{
|
|
||||||
"fieldGroup": {
|
|
||||||
"borderColor": "",
|
|
||||||
"height": 25,
|
|
||||||
"selectors": {
|
|
||||||
":disabled": {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"borderColor": undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -548,34 +520,6 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
|||||||
Non-hierarchically partitioned container.
|
Non-hierarchically partitioned container.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
|
||||||
tokens={
|
|
||||||
{
|
|
||||||
"childrenGap": 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
defaultValue="/id"
|
|
||||||
disabled={true}
|
|
||||||
label="Unique keys"
|
|
||||||
styles={
|
|
||||||
{
|
|
||||||
"fieldGroup": {
|
|
||||||
"borderColor": "",
|
|
||||||
"height": 25,
|
|
||||||
"selectors": {
|
|
||||||
":disabled": {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"borderColor": undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -825,34 +769,6 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
|
|||||||
Non-hierarchically partitioned container.
|
Non-hierarchically partitioned container.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
|
||||||
tokens={
|
|
||||||
{
|
|
||||||
"childrenGap": 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
defaultValue="/id"
|
|
||||||
disabled={true}
|
|
||||||
label="Unique keys"
|
|
||||||
styles={
|
|
||||||
{
|
|
||||||
"fieldGroup": {
|
|
||||||
"borderColor": "",
|
|
||||||
"height": 25,
|
|
||||||
"selectors": {
|
|
||||||
":disabled": {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"borderColor": undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1167,34 +1083,6 @@ exports[`SubSettingsComponent renders 1`] = `
|
|||||||
Non-hierarchically partitioned container.
|
Non-hierarchically partitioned container.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
|
||||||
tokens={
|
|
||||||
{
|
|
||||||
"childrenGap": 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
defaultValue="/id"
|
|
||||||
disabled={true}
|
|
||||||
label="Unique keys"
|
|
||||||
styles={
|
|
||||||
{
|
|
||||||
"fieldGroup": {
|
|
||||||
"borderColor": "",
|
|
||||||
"height": 25,
|
|
||||||
"selectors": {
|
|
||||||
":disabled": {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"borderColor": undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1483,33 +1371,5 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
|||||||
Non-hierarchically partitioned container.
|
Non-hierarchically partitioned container.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack
|
|
||||||
tokens={
|
|
||||||
{
|
|
||||||
"childrenGap": 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledTextFieldBase
|
|
||||||
defaultValue="/id"
|
|
||||||
disabled={true}
|
|
||||||
label="Unique keys"
|
|
||||||
styles={
|
|
||||||
{
|
|
||||||
"fieldGroup": {
|
|
||||||
"borderColor": "",
|
|
||||||
"height": 25,
|
|
||||||
"selectors": {
|
|
||||||
":disabled": {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"borderColor": undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"width": 300,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -17,15 +17,7 @@ export const collection = {
|
|||||||
includedPaths: [],
|
includedPaths: [],
|
||||||
excludedPaths: [],
|
excludedPaths: [],
|
||||||
}),
|
}),
|
||||||
rawDataModel: {
|
uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
|
||||||
uniqueKeyPolicy: {
|
|
||||||
uniqueKeys: [
|
|
||||||
{
|
|
||||||
paths: ["/id"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
usageSizeInKB: ko.observable(100),
|
usageSizeInKB: ko.observable(100),
|
||||||
offer: ko.observable<DataModels.Offer>({
|
offer: ko.observable<DataModels.Offer>({
|
||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
|
|||||||
@@ -71,18 +71,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyProperties": [
|
"partitionKeyProperties": [
|
||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
|
||||||
"uniqueKeyPolicy": {
|
|
||||||
"uniqueKeys": [
|
|
||||||
{
|
|
||||||
"paths": [
|
|
||||||
"/id",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
|
"uniqueKeyPolicy": {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
"vectorEmbeddingPolicy": [Function],
|
"vectorEmbeddingPolicy": [Function],
|
||||||
}
|
}
|
||||||
@@ -163,18 +153,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyProperties": [
|
"partitionKeyProperties": [
|
||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
|
||||||
"uniqueKeyPolicy": {
|
|
||||||
"uniqueKeys": [
|
|
||||||
{
|
|
||||||
"paths": [
|
|
||||||
"/id",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
|
"uniqueKeyPolicy": {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
"vectorEmbeddingPolicy": [Function],
|
"vectorEmbeddingPolicy": [Function],
|
||||||
}
|
}
|
||||||
@@ -294,18 +274,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyProperties": [
|
"partitionKeyProperties": [
|
||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
|
||||||
"uniqueKeyPolicy": {
|
|
||||||
"uniqueKeys": [
|
|
||||||
{
|
|
||||||
"paths": [
|
|
||||||
"/id",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
|
"uniqueKeyPolicy": {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
"vectorEmbeddingPolicy": [Function],
|
"vectorEmbeddingPolicy": [Function],
|
||||||
}
|
}
|
||||||
@@ -434,18 +404,8 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"partitionKeyProperties": [
|
"partitionKeyProperties": [
|
||||||
"partitionKey",
|
"partitionKey",
|
||||||
],
|
],
|
||||||
"rawDataModel": {
|
|
||||||
"uniqueKeyPolicy": {
|
|
||||||
"uniqueKeys": [
|
|
||||||
{
|
|
||||||
"paths": [
|
|
||||||
"/id",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
|
"uniqueKeyPolicy": {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
"vectorEmbeddingPolicy": [Function],
|
"vectorEmbeddingPolicy": [Function],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -283,43 +284,12 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openInVsCode(): void {
|
public openInVsCode(): void {
|
||||||
TelemetryProcessor.traceStart(Action.OpenVSCode);
|
|
||||||
this.openVsCodeButtonClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
private openVsCodeButtonClick(): void {
|
|
||||||
const activeTab = useTabs.getState().activeTab;
|
const activeTab = useTabs.getState().activeTab;
|
||||||
const resourceId = encodeURIComponent(userContext.databaseAccount.id);
|
const resourceId = encodeURIComponent(userContext.databaseAccount.id);
|
||||||
const database = encodeURIComponent(activeTab?.collection?.databaseId);
|
const database = encodeURIComponent(activeTab?.collection?.databaseId);
|
||||||
const container = encodeURIComponent(activeTab?.collection?.id());
|
const container = encodeURIComponent(activeTab?.collection?.id());
|
||||||
const baseUrl = `vscod://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
|
const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
|
||||||
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
|
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
|
||||||
const startTime = Date.now();
|
|
||||||
let vsCodeNotOpened = false;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
const timeOutTime = Date.now() - startTime;
|
|
||||||
if (!vsCodeNotOpened && timeOutTime < 1050) {
|
|
||||||
vsCodeNotOpened = true;
|
|
||||||
useDialog.getState().openDialog(openVSCodeDialogProps);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.href = vscodeUrl;
|
|
||||||
link.rel = "noopener noreferrer";
|
|
||||||
document.body.appendChild(link);
|
|
||||||
|
|
||||||
try {
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
TelemetryProcessor.traceStart(Action.OpenVSCode);
|
|
||||||
} catch (error) {
|
|
||||||
if (!vsCodeNotOpened) {
|
|
||||||
vsCodeNotOpened = true;
|
|
||||||
logConsoleError(`Failed to open VS Code: ${getErrorMessage(error)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const openVSCodeDialogProps: DialogProps = {
|
const openVSCodeDialogProps: DialogProps = {
|
||||||
linkProps: {
|
linkProps: {
|
||||||
@@ -334,15 +304,19 @@ export default class Explorer {
|
|||||||
secondaryButtonText: "Cancel",
|
secondaryButtonText: "Cancel",
|
||||||
|
|
||||||
onPrimaryButtonClick: () => {
|
onPrimaryButtonClick: () => {
|
||||||
vsCodeNotOpened = false;
|
try {
|
||||||
this.openVsCodeButtonClick();
|
window.location.href = vscodeUrl;
|
||||||
useDialog.getState().closeDialog();
|
TelemetryProcessor.traceStart(Action.OpenVSCode);
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleError(`Failed to open VS Code: ${getErrorMessage(error)}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onSecondaryButtonClick: () => {
|
onSecondaryButtonClick: () => {
|
||||||
useDialog.getState().closeDialog();
|
useDialog.getState().closeDialog();
|
||||||
TelemetryProcessor.traceCancel(Action.OpenVSCode);
|
TelemetryProcessor.traceCancel(Action.OpenVSCode);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
useDialog.getState().openDialog(openVSCodeDialogProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openCESCVAFeedbackBlade(): Promise<void> {
|
public async openCESCVAFeedbackBlade(): Promise<void> {
|
||||||
@@ -1196,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ 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);
|
||||||
|
|
||||||
const addVsCode = createOpenVsCodeDialogButton(container);
|
const addVsCode = createOpenVsCodeDialogButton(container);
|
||||||
return [openVCoreMongoTerminalButton, addVsCode];
|
return [openVCoreMongoTerminalButton, addVsCode];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
<RightPaneForm {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className={`paneMainContent ${styles.container}`}>
|
<div className={`paneMainContent ${styles.container}`}>
|
||||||
{!isFabricNative() && (
|
{!isFabricNative() && (
|
||||||
<Accordion className={`customAccordion ${styles.firstItem}`} collapsible>
|
<Accordion className={`customAccordion ${styles.firstItem}`}>
|
||||||
{shouldShowQueryPageOptions && (
|
{shouldShowQueryPageOptions && (
|
||||||
<AccordionItem value="1">
|
<AccordionItem value="1">
|
||||||
<AccordionHeader>
|
<AccordionHeader>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<Accordion
|
<Accordion
|
||||||
className="customAccordion ___1uf6361_0000000 fz7g6wx"
|
className="customAccordion ___1uf6361_0000000 fz7g6wx"
|
||||||
collapsible={true}
|
|
||||||
>
|
>
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
value="1"
|
value="1"
|
||||||
@@ -574,7 +573,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
|||||||
>
|
>
|
||||||
<Accordion
|
<Accordion
|
||||||
className="customAccordion ___1uf6361_0000000 fz7g6wx"
|
className="customAccordion ___1uf6361_0000000 fz7g6wx"
|
||||||
collapsible={true}
|
|
||||||
>
|
>
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
value="7"
|
value="7"
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
value=""
|
value=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField is-required root-116"
|
className="ms-TextField is-required root-110"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
@@ -647,7 +647,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="ms-Label root-127"
|
className="ms-Label root-121"
|
||||||
htmlFor="TextField0"
|
htmlFor="TextField0"
|
||||||
id="TextFieldLabel2"
|
id="TextFieldLabel2"
|
||||||
>
|
>
|
||||||
@@ -656,13 +656,13 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
</LabelBase>
|
</LabelBase>
|
||||||
</StyledLabelBase>
|
</StyledLabelBase>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-117"
|
className="ms-TextField-fieldGroup fieldGroup-111"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-labelledby="TextFieldLabel2"
|
aria-labelledby="TextFieldLabel2"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-118"
|
className="ms-TextField-field field-112"
|
||||||
id="TextField0"
|
id="TextField0"
|
||||||
name="collectionIdConfirmation"
|
name="collectionIdConfirmation"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
@@ -2464,7 +2464,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Create"
|
aria-label="Create"
|
||||||
className="ms-Button ms-Button--primary root-128"
|
className="ms-Button ms-Button--primary root-122"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-test="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
@@ -2477,14 +2477,14 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-129"
|
className="ms-Button-flexContainer flexContainer-123"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-130"
|
className="ms-Button-textContainer textContainer-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-132"
|
className="ms-Button-label label-126"
|
||||||
id="id__5"
|
id="id__5"
|
||||||
key="id__5"
|
key="id__5"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,13 +2,9 @@ import {
|
|||||||
DetailsList,
|
DetailsList,
|
||||||
DetailsListLayoutMode,
|
DetailsListLayoutMode,
|
||||||
DirectionalHint,
|
DirectionalHint,
|
||||||
FontIcon,
|
|
||||||
IColumn,
|
IColumn,
|
||||||
SelectionMode,
|
SelectionMode,
|
||||||
TooltipHost,
|
TooltipHost,
|
||||||
getTheme,
|
|
||||||
mergeStyles,
|
|
||||||
mergeStyleSets,
|
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { Upload } from "Common/Upload/Upload";
|
import { Upload } from "Common/Upload/Upload";
|
||||||
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
import { UploadDetailsRecord } from "Contracts/ViewModels";
|
||||||
@@ -18,36 +14,6 @@ import { getErrorMessage } from "../../Tables/Utilities";
|
|||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
const theme = getTheme();
|
|
||||||
const iconClass = mergeStyles({
|
|
||||||
verticalAlign: "middle",
|
|
||||||
maxHeight: "16px",
|
|
||||||
maxWidth: "16px",
|
|
||||||
});
|
|
||||||
|
|
||||||
const classNames = mergeStyleSets({
|
|
||||||
fileIconHeaderIcon: {
|
|
||||||
padding: 0,
|
|
||||||
fontSize: "16px",
|
|
||||||
},
|
|
||||||
fileIconCell: {
|
|
||||||
textAlign: "center",
|
|
||||||
selectors: {
|
|
||||||
"&:before": {
|
|
||||||
content: ".",
|
|
||||||
display: "inline-block",
|
|
||||||
verticalAlign: "middle",
|
|
||||||
height: "100%",
|
|
||||||
width: "0px",
|
|
||||||
visibility: "hidden",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
error: [{ color: theme.semanticColors.errorIcon }, iconClass],
|
|
||||||
accept: [{ color: theme.semanticColors.successIcon }, iconClass],
|
|
||||||
warning: [{ color: theme.semanticColors.warningIcon }, iconClass],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UploadItemsPane: FunctionComponent = () => {
|
export const UploadItemsPane: FunctionComponent = () => {
|
||||||
const [files, setFiles] = useState<FileList>();
|
const [files, setFiles] = useState<FileList>();
|
||||||
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
|
||||||
@@ -94,94 +60,44 @@ export const UploadItemsPane: FunctionComponent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: IColumn[] = [
|
const columns: IColumn[] = [
|
||||||
{
|
|
||||||
key: "icons",
|
|
||||||
name: "",
|
|
||||||
fieldName: "",
|
|
||||||
className: classNames.fileIconCell,
|
|
||||||
iconClassName: classNames.fileIconHeaderIcon,
|
|
||||||
isIconOnly: true,
|
|
||||||
minWidth: 16,
|
|
||||||
maxWidth: 16,
|
|
||||||
onRender: (item: UploadDetailsRecord, index: number, column: IColumn) => {
|
|
||||||
if (item.numFailed) {
|
|
||||||
const errorList = (
|
|
||||||
<ul
|
|
||||||
aria-label={"error list"}
|
|
||||||
style={{
|
|
||||||
margin: "5px 0",
|
|
||||||
paddingLeft: "20px",
|
|
||||||
listStyleType: "disc", // Explicitly set to use bullets (dots)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.errors.map((error, i) => (
|
|
||||||
<li key={i} style={{ display: "list-item" }}>
|
|
||||||
{error}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TooltipHost
|
|
||||||
content={errorList}
|
|
||||||
id={`tooltip-${index}-${column.key}`}
|
|
||||||
directionalHint={DirectionalHint.bottomAutoEdge}
|
|
||||||
>
|
|
||||||
<FontIcon iconName="Error" className={classNames.error} aria-label="error" />
|
|
||||||
</TooltipHost>
|
|
||||||
);
|
|
||||||
} else if (item.numThrottled) {
|
|
||||||
return <FontIcon iconName="Warning" className={classNames.warning} aria-label="warning" />;
|
|
||||||
} else {
|
|
||||||
return <FontIcon iconName="Accept" className={classNames.accept} aria-label="accept" />;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "fileName",
|
key: "fileName",
|
||||||
name: "FILE NAME",
|
name: "FILE NAME",
|
||||||
fieldName: "fileName",
|
fieldName: "fileName",
|
||||||
minWidth: 120,
|
minWidth: 140,
|
||||||
maxWidth: 140,
|
maxWidth: 140,
|
||||||
onRender: (item: UploadDetailsRecord, index: number, column: IColumn) => {
|
|
||||||
const fieldContent = item.fileName;
|
|
||||||
return (
|
|
||||||
<TooltipHost
|
|
||||||
content={fieldContent}
|
|
||||||
id={`tooltip-${index}-${column.key}`}
|
|
||||||
directionalHint={DirectionalHint.bottomAutoEdge}
|
|
||||||
>
|
|
||||||
{fieldContent}
|
|
||||||
</TooltipHost>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "status",
|
key: "status",
|
||||||
name: "STATUS",
|
name: "STATUS",
|
||||||
fieldName: "numSucceeded",
|
fieldName: "numSucceeded",
|
||||||
minWidth: 120,
|
minWidth: 140,
|
||||||
maxWidth: 140,
|
maxWidth: 140,
|
||||||
isRowHeader: true,
|
isRowHeader: true,
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
data: "string",
|
data: "string",
|
||||||
isPadded: true,
|
isPadded: true,
|
||||||
onRender: (item: UploadDetailsRecord, index: number, column: IColumn) => {
|
|
||||||
const fieldContent = `${item.numSucceeded} created, ${item.numThrottled} throttled, ${item.numFailed} errors`;
|
|
||||||
return (
|
|
||||||
<TooltipHost
|
|
||||||
content={fieldContent}
|
|
||||||
id={`tooltip-${index}-${column.key}`}
|
|
||||||
directionalHint={DirectionalHint.bottomAutoEdge}
|
|
||||||
>
|
|
||||||
{fieldContent}
|
|
||||||
</TooltipHost>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const _renderItemColumn = (item: UploadDetailsRecord, index: number, column: IColumn) => {
|
||||||
|
let fieldContent: string;
|
||||||
|
const tooltipId = `tooltip-${index}-${column.key}`;
|
||||||
|
|
||||||
|
switch (column.key) {
|
||||||
|
case "status":
|
||||||
|
fieldContent = `${item.numSucceeded} created, ${item.numThrottled} throttled, ${item.numFailed} errors`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fieldContent = item.fileName;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TooltipHost content={fieldContent} id={tooltipId} directionalHint={DirectionalHint.rightCenter}>
|
||||||
|
{fieldContent}
|
||||||
|
</TooltipHost>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="paneMainContent">
|
<div className="paneMainContent">
|
||||||
@@ -199,6 +115,7 @@ export const UploadItemsPane: FunctionComponent = () => {
|
|||||||
<DetailsList
|
<DetailsList
|
||||||
items={uploadFileData}
|
items={uploadFileData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
onRenderItemColumn={_renderItemColumn}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
isHeaderVisible={true}
|
isHeaderVisible={true}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -729,7 +729,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
} else if (result.statusCode >= 400) {
|
} else if (result.statusCode >= 400) {
|
||||||
newFailed.push(result.documentId);
|
newFailed.push(result.documentId);
|
||||||
logConsoleError(
|
logConsoleError(
|
||||||
`Failed to delete document ${result.documentId.id()} with status code ${result.statusCode}`,
|
`Failed to delete document ${result.documentId.id} with status code ${result.statusCode}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
apiEndpoint: apiEndpoint,
|
apiEndpoint: apiEndpoint,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"https://localhost:443",
|
window.origin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ export function getMongoShellUrl(): string {
|
|||||||
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
|
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
|
||||||
const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
||||||
|
|
||||||
return `https://localhost:443/index.html?${queryString}`;
|
return `/mongoshell/index.html?${queryString}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1122,9 +1122,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
stats.numSucceeded++;
|
stats.numSucceeded++;
|
||||||
} else if (response.statusCode === 429) {
|
} else if (response.statusCode === 429) {
|
||||||
documentsToAttempt.push(attemptedDocuments[index]);
|
documentsToAttempt.push(attemptedDocuments[index]);
|
||||||
} else if (response.statusCode === 409) {
|
|
||||||
stats.numFailed++;
|
|
||||||
stats.errors.push(`Document with id ${attemptedDocuments[index].id} already exists.`);
|
|
||||||
} else {
|
} else {
|
||||||
stats.numFailed++;
|
stats.numFailed++;
|
||||||
stats.errors.push(JSON.stringify(response.resourceBody));
|
stats.errors.push(JSON.stringify(response.resourceBody));
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export function validateEndpoint(
|
|||||||
endpointToValidate: string | undefined,
|
endpointToValidate: string | undefined,
|
||||||
allowedEndpoints: ReadonlyArray<string>,
|
allowedEndpoints: ReadonlyArray<string>,
|
||||||
): boolean {
|
): boolean {
|
||||||
return true;
|
|
||||||
try {
|
try {
|
||||||
return validateEndpointInternal(
|
return validateEndpointInternal(
|
||||||
endpointToValidate,
|
endpointToValidate,
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ export function isInvalidParentFrameOrigin(event: MessageEvent): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isValidOrigin(allowedOrigins: ReadonlyArray<string>, event: MessageEvent): boolean {
|
function isValidOrigin(allowedOrigins: ReadonlyArray<string>, event: MessageEvent): boolean {
|
||||||
return true;
|
|
||||||
const eventOrigin = (event && event.origin) || "";
|
const eventOrigin = (event && event.origin) || "";
|
||||||
const windowOrigin = (window && window.origin) || "";
|
const windowOrigin = (window && window.origin) || "";
|
||||||
if (eventOrigin === windowOrigin) {
|
if (eventOrigin === windowOrigin) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<clear />
|
<clear />
|
||||||
<add name="X-Xss-Protection" value="1; mode=block" />
|
<add name="X-Xss-Protection" value="1; mode=block" />
|
||||||
<add name="X-Content-Type-Options" value="nosniff" />
|
<add name="X-Content-Type-Options" value="nosniff" />
|
||||||
<add name="Content-Security-Policy" value="frame-src 'vscode:' frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com *.analysis-df.windows.net dataexplorer-preview.azurewebsites.net" />
|
<add name="Content-Security-Policy" value="frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com *.analysis-df.windows.net dataexplorer-preview.azurewebsites.net" />
|
||||||
</customHeaders>
|
</customHeaders>
|
||||||
<redirectHeaders>
|
<redirectHeaders>
|
||||||
<clear />
|
<clear />
|
||||||
|
|||||||
Reference in New Issue
Block a user