mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 02:41:39 +00:00
Compare commits
43 Commits
users/sour
...
fix_system
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b585cdddb4 | ||
|
|
6b8998eeaf | ||
|
|
f3b582a911 | ||
|
|
7d6efe8e2e | ||
|
|
c51fd8bec0 | ||
|
|
c1d5641c60 | ||
|
|
fa8c4462e8 | ||
|
|
97055ccbf1 | ||
|
|
9cbae73f58 | ||
|
|
589adccd1d | ||
|
|
5ad5c5f398 | ||
|
|
12b8523a0a | ||
|
|
003db031ea | ||
|
|
68c42fb361 | ||
|
|
e90e1fc581 | ||
|
|
8bcad6e0e0 | ||
|
|
9f3236c29c | ||
|
|
2f858ecf9b | ||
|
|
b13517f218 | ||
|
|
ad481b7c2c | ||
|
|
ab27e2fb11 | ||
|
|
f678a66d5c | ||
|
|
1fe2a311c5 | ||
|
|
cabaa5ebed | ||
|
|
fcc98f1a5a | ||
|
|
0f8c6bd524 | ||
|
|
92b288eee7 | ||
|
|
ed4e2f9beb | ||
|
|
0c7985ce89 | ||
|
|
1f8fefb732 | ||
|
|
41d690748b | ||
|
|
0d81d4270e | ||
|
|
274c85d2de | ||
|
|
d9436be61b | ||
|
|
6db2536a61 | ||
|
|
71799d7ec5 | ||
|
|
ace8aadbbd | ||
|
|
714f38a1be | ||
|
|
5e4ce3dcd5 | ||
|
|
b9ed554377 | ||
|
|
5a7de7ded4 | ||
|
|
bc183bafdb | ||
|
|
af4e1d10b4 |
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@@ -164,24 +164,24 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shardTotal: [8]
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
||||
shardTotal: [16]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Az CLI login"
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps
|
||||
- name: "Az CLI login"
|
||||
uses: Azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 2.1 KiB |
8
images/golang.svg
Normal file
8
images/golang.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path fill="#8CC5E7" d="M21.4679537,3.20617761 C22.1814672,4.67953668 20.0131274,4.83706564 20.1243243,5.49498069 C20.3281853,6.68108108 20.1891892,8.44169884 20.0316602,10.1745174 C19.7629344,13.1119691 21.9590734,20.1451737 17.3814672,22.9714286 C16.5196911,23.5088803 14.4718147,23.8054054 12.4517375,23.8517375 C12.4517375,23.8517375 12.442471,23.8517375 12.442471,23.8517375 C12.442471,23.8517375 12.4332046,23.8517375 12.4332046,23.8517375 C10.4131274,23.8054054 8.08725869,23.5088803 7.22548263,22.9714286 C2.65714286,20.1451737 4.85328185,13.1119691 4.59382239,10.1745174 C4.42702703,8.44169884 4.28803089,6.68108108 4.5011583,5.49498069 C4.61235521,4.83706564 2.44401544,4.68880309 3.15752896,3.20617761 C3.76911197,1.93667954 5.27953668,3.05791506 5.65945946,2.65945946 C7.596139,0.648648649 9.94980695,0.111196911 11.8030888,0.0648648649 C11.988417,0.0648648649 12.8223938,0.0648648649 12.8223938,0.0648648649 C14.6664093,0.157528958 17.0200772,0.657915058 18.9660232,2.65945946 C19.3459459,3.05791506 20.8471042,1.93667954 21.4679537,3.20617761 Z M11.4324324,10.9065637 C11.3490347,10.9436293 11.2100386,11.8517375 11.6362934,11.8980695 C11.9235521,11.9258687 12.7111969,12.0185328 12.8965251,11.8980695 C13.2579151,11.6664093 13.2208494,11.1104247 13.0169884,10.9714286 C12.6741313,10.7490347 11.5250965,10.8602317 11.4324324,10.9065637 Z M9.07876448,4.10501931 C8.12432432,3.99382239 6.52123552,4.88339768 6.28030888,6.77374517 C6.02084942,8.73822394 8.33745174,10.6841699 10.56139,8.73822394 C11.7567568,7.69111969 12.1737452,4.46640927 9.07876448,4.10501931 Z M15.5281853,4.10501931 C12.4332046,4.46640927 12.8501931,7.69111969 14.0455598,8.73822394 C16.2694981,10.6841699 18.5861004,8.73822394 18.3266409,6.77374517 C18.0949807,4.88339768 16.4918919,3.99382239 15.5281853,4.10501931 Z"/>
|
||||
<path fill="#B8937F" d="M12.3127413,8.98841699 C12.8965251,8.90501931 14.2957529,9.57220077 14.2030888,10.3598456 C14.0918919,11.2772201 10.5984556,11.3976834 10.4131274,10.3042471 C10.3019305,9.63706564 10.8301158,9.21081081 12.3127413,8.98841699 Z M20.1984556,16.3737452 C19.9111969,16.3644788 19.7258687,15.984556 19.7258687,15.7528958 C19.7258687,15.3359073 19.7814672,14.8447876 20.0872587,14.6316602 C20.7173745,14.196139 21.2177606,16.3830116 20.1984556,16.3737452 Z M4.41776062,16.3737452 C3.3984556,16.3830116 3.8988417,14.196139 4.52895753,14.6316602 C4.83474903,14.8447876 4.89034749,15.3359073 4.89034749,15.7528958 C4.89034749,15.984556 4.70501931,16.3644788 4.41776062,16.3737452 Z M18.2617761,23.0918919 C18.4471042,23.3606178 18.4563707,23.5459459 18.1598456,23.6849421 C17.0293436,24.203861 16.019305,23.5088803 16.3992278,23.3142857 C17.2054054,22.9065637 17.7057915,22.2671815 18.2617761,23.0918919 Z M6.35444015,23.184556 C6.91042471,22.3598456 7.41081081,22.9992278 8.21698842,23.4069498 C8.5969112,23.6015444 7.58687259,24.2965251 6.45637066,23.7776062 C6.15984556,23.63861 6.16911197,23.4532819 6.35444015,23.184556 Z"/>
|
||||
<path fill="#000000" d="M19.7351351,3.42857143 C19.7814672,3.23397683 20.2633205,3.14131274 20.5320463,3.47490347 C20.8563707,3.87335907 20.0594595,4.42007722 20.0223938,4.1976834 C19.9297297,3.5953668 19.6795367,3.62316602 19.7351351,3.42857143 Z M4.88108108,3.42857143 C4.93667954,3.62316602 4.68648649,3.5953668 4.59382239,4.1976834 C4.55675676,4.42007722 3.75984556,3.87335907 4.08416988,3.47490347 C4.34362934,3.14131274 4.82548263,3.23397683 4.88108108,3.42857143 Z M15.7413127,7.94131274 C15.1578953,7.94131274 14.6849421,7.46835949 14.6849421,6.88494208 C14.6849421,6.30152468 15.1578953,5.82857143 15.7413127,5.82857143 C16.3247301,5.82857143 16.7976834,6.30152468 16.7976834,6.88494208 C16.7976834,7.46835949 16.3247301,7.94131274 15.7413127,7.94131274 Z M15.4633205,6.76447876 C15.6475575,6.76447876 15.7969112,6.61512511 15.7969112,6.43088803 C15.7969112,6.24665096 15.6475575,6.0972973 15.4633205,6.0972973 C15.2790834,6.0972973 15.1297297,6.24665096 15.1297297,6.43088803 C15.1297297,6.61512511 15.2790834,6.76447876 15.4633205,6.76447876 Z M11.3583012,9.43320463 C11.4694981,9.00694981 11.8586873,8.86795367 12.1737452,8.85868726 C12.9799228,8.84015444 13.2857143,9.27567568 13.3135135,9.61853282 C13.369112,10.2023166 11.1081081,10.3413127 11.3583012,9.43320463 Z M8.87490347,7.94131274 C8.29148607,7.94131274 7.81853282,7.46835949 7.81853282,6.88494208 C7.81853282,6.30152468 8.29148607,5.82857143 8.87490347,5.82857143 C9.45832088,5.82857143 9.93127413,6.30152468 9.93127413,6.88494208 C9.93127413,7.46835949 9.45832088,7.94131274 8.87490347,7.94131274 Z M9.15289575,6.76447876 C9.33713283,6.76447876 9.48648649,6.61512511 9.48648649,6.43088803 C9.48648649,6.24665096 9.33713283,6.0972973 9.15289575,6.0972973 C8.96865868,6.0972973 8.81930502,6.24665096 8.81930502,6.43088803 C8.81930502,6.61512511 8.96865868,6.76447876 9.15289575,6.76447876 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
10
images/springboot.svg
Normal file
10
images/springboot.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M38.9437824,35.879008 C89.5234256,-13.1200214 170.398168,-11.8028432 219.397197,39.0402357 C224.929346,31.6640377 229.671187,23.4975328 233.095851,15.0675923 C249.165425,64.0666217 258.912543,105.162582 255.224444,137.038295 C253.380395,163.90873 242.842969,189.725423 225.456217,210.273403 C180.145286,264.014274 99.53398,270.863601 45.7931091,225.55267 L45.7931091,225.55267 L44.765,224.638 L44.7103323,224.601984 C44.5420247,224.484832 44.376007,224.362668 44.2124952,224.235492 C43.7219599,223.853965 43.2765312,223.438607 42.8762093,222.995252 L42.732,222.831 L41.0512675,221.3377 C39.4121124,219.93271 37.7729573,218.52772 36.3188215,216.93771 L35.7825547,216.332423 C-13.2164747,165.752779 -11.6358609,84.8780374 38.9437824,35.879008 Z M57.9111486,207.375611 C53.169307,203.687512 46.3199803,204.214383 42.6318814,208.956225 C39.3888978,213.125775 39.4048731,218.924805 42.6798072,222.771269 L42.732,222.831 L44.765,224.638 L44.9644841,224.773953 C49.5691585,227.80174 55.7644273,227.175885 59.2982065,222.896387 L59.4917624,222.654878 C63.1798614,217.913037 62.3895545,211.06371 57.9111486,207.375611 Z M231.778672,28.2393744 C218.60689,55.9001168 185.940871,76.9749681 157.753257,83.5608592 C131.146257,89.8833146 107.963921,84.6146018 83.4644059,94.0982849 C27.6160498,115.436572 28.6697923,181.822354 59.2283268,196.838185 L59.2283268,196.838185 L61.0723763,197.891928 C61.0723763,197.891928 83.1456487,193.50309 104.973663,187.707242 L106.843514,187.207079 C115.561826,184.857554 124.138869,182.296538 131.146257,179.714869 C167.500376,166.279651 207.542593,133.08676 220.714375,94.6251562 C213.865049,134.667374 179.35498,173.392413 144.84491,191.042601 C126.404416,200.526284 112.178891,202.633769 81.883792,213.171195 C78.195693,214.488373 75.297901,215.805551 75.297901,215.805551 C75.6675607,215.754564 76.0372203,215.70481 76.4060145,215.65629 L77.1421925,215.560893 L77.1421925,215.560893 L77.8745239,215.468787 C84.5652297,214.639554 90.5771682,214.224938 90.5771682,214.224938 C133.517178,212.117452 200.956702,226.342977 232.305544,184.45671 C264.444692,141.780136 246.531068,72.7599979 231.778672,28.2393744 Z" fill="#6DB33F">
|
||||
|
||||
</path>
|
||||
<path d="M57.9111486,207.375611 C62.3895545,211.06371 63.1798614,217.913037 59.4917624,222.654878 C55.8036635,227.39672 48.9543368,227.923591 44.2124952,224.235492 C39.4706537,220.547393 38.9437824,213.698066 42.6318814,208.956225 C46.3199803,204.214383 53.169307,203.687512 57.9111486,207.375611 Z M231.778672,28.2393744 C246.531068,72.7599979 264.444692,141.780136 232.305544,184.45671 C200.956702,226.342977 133.517178,212.117452 90.5771682,214.224938 C90.5771682,214.224938 84.5652297,214.639554 77.8745239,215.468787 L77.1421925,215.560893 C76.5300999,215.63902 75.9140004,215.720572 75.297901,215.805551 C75.297901,215.805551 78.195693,214.488373 81.883792,213.171195 C112.178891,202.633769 126.404416,200.526284 144.84491,191.042601 C179.35498,173.392413 213.865049,134.667374 220.714375,94.6251562 C207.542593,133.08676 167.500376,166.279651 131.146257,179.714869 C106.119871,188.935116 61.0723763,197.891928 61.0723763,197.891928 L59.2283268,196.838185 C28.6697923,181.822354 27.6160498,115.436572 83.4644059,94.0982849 C107.963921,84.6146018 131.146257,89.8833146 157.753257,83.5608592 C185.940871,76.9749681 218.60689,55.9001168 231.778672,28.2393744 Z" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
1322
less/quickstart.less
1322
less/quickstart.less
File diff suppressed because it is too large
Load Diff
@@ -37,20 +37,51 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
launchOptions: {
|
||||
firefoxUserPrefs: {
|
||||
"security.fileuri.strict_origin_policy": false,
|
||||
"network.http.referer.XOriginPolicy": 0,
|
||||
"network.http.referer.trimmingPolicy": 0,
|
||||
"privacy.file_unique_origin": false,
|
||||
"security.csp.enable": false,
|
||||
"network.cors_preflight.allow_client_cert": true,
|
||||
"dom.security.https_first": false,
|
||||
"network.http.cross-origin-embedder-policy": false,
|
||||
"network.http.cross-origin-opener-policy": false,
|
||||
"browser.tabs.remote.useCrossOriginPolicy": false,
|
||||
"browser.tabs.remote.useCORP": false,
|
||||
},
|
||||
args: ["--disable-web-security"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webkit",
|
||||
use: { ...devices["Desktop Safari"] },
|
||||
use: {
|
||||
...devices["Desktop Safari"],
|
||||
},
|
||||
},
|
||||
/* Test against branded browsers. */
|
||||
{
|
||||
name: "Google Chrome",
|
||||
use: { ...devices["Desktop Chrome"], channel: "chrome" }, // or 'chrome-beta'
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
channel: "chrome",
|
||||
launchOptions: {
|
||||
args: ["--disable-web-security", "--disable-features=IsolateOrigins,site-per-process"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Microsoft Edge",
|
||||
use: { ...devices["Desktop Edge"], channel: "msedge" }, // or 'msedge-dev'
|
||||
use: {
|
||||
...devices["Desktop Edge"],
|
||||
channel: "msedge",
|
||||
launchOptions: {
|
||||
args: ["--disable-web-security", "--disable-features=IsolateOrigins,site-per-process"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@@ -18,10 +18,13 @@ export type DataExploreMessageV3 =
|
||||
| {
|
||||
type: FabricMessageTypes.GetAllResourceTokens;
|
||||
id: string;
|
||||
}
|
||||
| {
|
||||
type: FabricMessageTypes.OpenSettings;
|
||||
settingsId: string;
|
||||
};
|
||||
|
||||
export type GetCosmosTokenMessageOptions = {
|
||||
export interface GetCosmosTokenMessageOptions {
|
||||
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
||||
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
|
||||
resourceId: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export enum FabricMessageTypes {
|
||||
GetAllResourceTokens = "GetAllResourceTokens",
|
||||
GetAccessToken = "GetAccessToken",
|
||||
Ready = "Ready",
|
||||
OpenSettings = "OpenSettings",
|
||||
}
|
||||
|
||||
export interface AuthorizationToken {
|
||||
|
||||
@@ -193,6 +193,7 @@ export const InputDataList: FC<InputDataListProps> = ({
|
||||
<>
|
||||
<Input
|
||||
id="filterInput"
|
||||
data-test={"DocumentsTab/FilterInput"}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
|
||||
@@ -1149,6 +1149,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
collection: this.collection,
|
||||
database: this.database,
|
||||
isFixedContainer: this.isFixedContainer,
|
||||
isGlobalSecondaryIndex: this.isGlobalSecondaryIndex,
|
||||
onThroughputChange: this.onThroughputChange,
|
||||
throughput: this.state.throughput,
|
||||
throughputBaseline: this.state.throughputBaseline,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { shallow } from "enzyme";
|
||||
import {
|
||||
PartitionKeyComponent,
|
||||
PartitionKeyComponentProps,
|
||||
} from "Explorer/Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import React from "react";
|
||||
|
||||
describe("PartitionKeyComponent", () => {
|
||||
// Create a test setup function to get fresh instances for each test
|
||||
const setupTest = () => {
|
||||
// Create an instance of the mocked Explorer
|
||||
const explorer = new Explorer();
|
||||
// Create minimal mock objects for database and collection
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockDatabase = {} as any as import("../../../../Contracts/ViewModels").Database;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockCollection = {} as any as import("../../../../Contracts/ViewModels").Collection;
|
||||
|
||||
// Create props with the mocked Explorer instance
|
||||
const props: PartitionKeyComponentProps = {
|
||||
database: mockDatabase,
|
||||
collection: mockCollection,
|
||||
explorer,
|
||||
};
|
||||
|
||||
return { explorer, props };
|
||||
};
|
||||
|
||||
it("renders default component and matches snapshot", () => {
|
||||
const { props } = setupTest();
|
||||
const wrapper = shallow(<PartitionKeyComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders read-only component and matches snapshot", () => {
|
||||
const { props } = setupTest();
|
||||
const wrapper = shallow(<PartitionKeyComponent {...props} isReadOnly={true} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -161,7 +161,7 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }} styles={{ root: { maxWidth: 600 } }}>
|
||||
<Stack tokens={{ childrenGap: 10 }}>
|
||||
<Text styles={textHeadingStyle}>Change {partitionKeyName.toLowerCase()}</Text>
|
||||
{!isReadOnly && <Text styles={textHeadingStyle}>Change {partitionKeyName.toLowerCase()}</Text>}
|
||||
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
||||
<Stack tokens={{ childrenGap: 5 }}>
|
||||
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text>
|
||||
|
||||
@@ -9,6 +9,7 @@ describe("ScaleComponent", () => {
|
||||
collection: collection,
|
||||
database: undefined,
|
||||
isFixedContainer: false,
|
||||
isGlobalSecondaryIndex: false,
|
||||
onThroughputChange: () => {
|
||||
return;
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface ScaleComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
database: ViewModels.Database;
|
||||
isFixedContainer: boolean;
|
||||
isGlobalSecondaryIndex: boolean;
|
||||
onThroughputChange: (newThroughput: number) => void;
|
||||
throughput: number;
|
||||
throughputBaseline: number;
|
||||
@@ -143,6 +144,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
throughputError={this.props.throughputError}
|
||||
instantMaximumThroughput={this.offer?.instantMaximumThroughput}
|
||||
softAllowedMaximumThroughput={this.offer?.softAllowedMaximumThroughput}
|
||||
isGlobalSecondaryIndex={this.props.isGlobalSecondaryIndex}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
||||
},
|
||||
instantMaximumThroughput: 5000,
|
||||
softAllowedMaximumThroughput: 1000000,
|
||||
isGlobalSecondaryIndex: false,
|
||||
};
|
||||
|
||||
it("throughput input visible", () => {
|
||||
|
||||
@@ -80,6 +80,7 @@ export interface ThroughputInputAutoPilotV3Props {
|
||||
throughputError?: string;
|
||||
instantMaximumThroughput: number;
|
||||
softAllowedMaximumThroughput: number;
|
||||
isGlobalSecondaryIndex: boolean;
|
||||
}
|
||||
|
||||
interface ThroughputInputAutoPilotV3State {
|
||||
@@ -375,22 +376,26 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
toolTipElement={getToolTipContainer(this.props.infoBubbleText)}
|
||||
/>
|
||||
</Label>
|
||||
{this.overrideWithProvisionedThroughputSettings() && (
|
||||
<MessageBar
|
||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||
styles={messageBarStyles}
|
||||
>
|
||||
{manualToAutoscaleDisclaimerElement}
|
||||
</MessageBar>
|
||||
{!this.props.isGlobalSecondaryIndex && (
|
||||
<>
|
||||
{this.overrideWithProvisionedThroughputSettings() && (
|
||||
<MessageBar
|
||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||
styles={messageBarStyles}
|
||||
>
|
||||
{manualToAutoscaleDisclaimerElement}
|
||||
</MessageBar>
|
||||
)}
|
||||
<ChoiceGroup
|
||||
selectedKey={this.props.isAutoPilotSelected.toString()}
|
||||
options={this.options}
|
||||
onChange={this.onChoiceGroupChange}
|
||||
required={this.props.showAsMandatory}
|
||||
ariaLabelledBy={labelId}
|
||||
styles={getChoiceGroupStyles(this.props.wasAutopilotOriginallySet, this.props.isAutoPilotSelected, true)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<ChoiceGroup
|
||||
selectedKey={this.props.isAutoPilotSelected.toString()}
|
||||
options={this.options}
|
||||
onChange={this.onChoiceGroupChange}
|
||||
required={this.props.showAsMandatory}
|
||||
ariaLabelledBy={labelId}
|
||||
styles={getChoiceGroupStyles(this.props.wasAutopilotOriginallySet, this.props.isAutoPilotSelected, true)}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PartitionKeyComponent renders default component and matches snapshot 1`] = `
|
||||
<Stack
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"fontSize": 16,
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Change
|
||||
partition key
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Current
|
||||
partition key
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Partitioning
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text />
|
||||
<Text>
|
||||
Non-hierarchical
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<StyledMessageBar
|
||||
messageBarType={5}
|
||||
>
|
||||
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the source container for the entire duration of the partition key change process.
|
||||
<StyledLinkBase
|
||||
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
|
||||
target="_blank"
|
||||
underline={true}
|
||||
>
|
||||
Learn more
|
||||
</StyledLinkBase>
|
||||
</StyledMessageBar>
|
||||
<Text>
|
||||
To change the partition key, a new destination container must be created or an existing destination container selected. Data will then be copied to the destination container.
|
||||
</Text>
|
||||
<CustomizedPrimaryButton
|
||||
onClick={[Function]}
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"width": "fit-content",
|
||||
},
|
||||
}
|
||||
}
|
||||
text="Change"
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`PartitionKeyComponent renders read-only component and matches snapshot 1`] = `
|
||||
<Stack
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"maxWidth": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Current
|
||||
partition key
|
||||
</Text>
|
||||
<Text
|
||||
styles={
|
||||
{
|
||||
"root": {
|
||||
"fontWeight": 600,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Partitioning
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
tokens={
|
||||
{
|
||||
"childrenGap": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text />
|
||||
<Text>
|
||||
Non-hierarchical
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
|
||||
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||
|
||||
const zeroValue = 0;
|
||||
@@ -165,7 +166,7 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
||||
case SettingsV2TabTypes.IndexingPolicyTab:
|
||||
return "Indexing Policy";
|
||||
case SettingsV2TabTypes.PartitionKeyTab:
|
||||
return "Partition Keys (preview)";
|
||||
return isFabricNative() ? "Partition Keys" : "Partition Keys (preview)";
|
||||
case SettingsV2TabTypes.ComputedPropertiesTab:
|
||||
return "Computed Properties";
|
||||
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
||||
|
||||
@@ -79,6 +79,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
}
|
||||
isAutoPilotSelected={false}
|
||||
isFixedContainer={false}
|
||||
isGlobalSecondaryIndex={true}
|
||||
onAutoPilotSelected={[Function]}
|
||||
onMaxAutoPilotThroughputChange={[Function]}
|
||||
onScaleDiscardableChange={[Function]}
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface ThroughputInputProps {
|
||||
isFreeTier: boolean;
|
||||
showFreeTierExceedThroughputTooltip: boolean;
|
||||
isQuickstart?: boolean;
|
||||
isGlobalSecondaryIndex?: boolean;
|
||||
setThroughputValue: (throughput: number) => void;
|
||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||
setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
|
||||
@@ -30,6 +31,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
isFreeTier,
|
||||
showFreeTierExceedThroughputTooltip,
|
||||
isQuickstart,
|
||||
isGlobalSecondaryIndex,
|
||||
setThroughputValue,
|
||||
setIsAutoscale,
|
||||
setIsThroughputCapExceeded,
|
||||
@@ -193,41 +195,41 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
</Text>
|
||||
<InfoTooltip>{PricingUtils.getRuToolTipText()}</InfoTooltip>
|
||||
</Stack>
|
||||
{!isGlobalSecondaryIndex && (
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<div role="radiogroup">
|
||||
<input
|
||||
id="Autoscale-input"
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label={`${getThroughputLabelText()} Autoscale`}
|
||||
aria-required={true}
|
||||
checked={isAutoscaleSelected}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||
/>
|
||||
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
|
||||
Autoscale
|
||||
</label>
|
||||
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<div role="radiogroup">
|
||||
<input
|
||||
id="Autoscale-input"
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label={`${getThroughputLabelText()} Autoscale`}
|
||||
aria-required={true}
|
||||
checked={isAutoscaleSelected}
|
||||
type="radio"
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||
/>
|
||||
<label htmlFor="Autoscale-input" className="throughputInputRadioBtnLabel">
|
||||
Autoscale
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="Manual-input"
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label={`${getThroughputLabelText()} Manual`}
|
||||
checked={!isAutoscaleSelected}
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
/>
|
||||
<label className="throughputInputRadioBtnLabel" htmlFor="Manual-input">
|
||||
Manual
|
||||
</label>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
<input
|
||||
id="Manual-input"
|
||||
className="throughputInputRadioBtn"
|
||||
aria-label={`${getThroughputLabelText()} Manual`}
|
||||
checked={!isAutoscaleSelected}
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
/>
|
||||
<label className="throughputInputRadioBtnLabel" htmlFor="Manual-input">
|
||||
Manual
|
||||
</label>
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
{isAutoscaleSelected && (
|
||||
<Stack className="throughputInputSpacing">
|
||||
<Text variant="small" aria-label="capacity calculator of azure cosmos db">
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
FullTextPolicyDefault,
|
||||
getPartitionKey,
|
||||
isSynapseLinkEnabled,
|
||||
parseUniqueKeys,
|
||||
scrollToSection,
|
||||
shouldShowAnalyticalStoreOptions,
|
||||
} from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||
@@ -35,7 +34,6 @@ import { AnalyticalStoreComponent } from "Explorer/Panes/AddGlobalSecondaryIndex
|
||||
import { FullTextSearchComponent } from "Explorer/Panes/AddGlobalSecondaryIndexPanel/Components/FullTextSearchComponent";
|
||||
import { PartitionKeyComponent } from "Explorer/Panes/AddGlobalSecondaryIndexPanel/Components/PartitionKeyComponent";
|
||||
import { ThroughputComponent } from "Explorer/Panes/AddGlobalSecondaryIndexPanel/Components/ThroughputComponent";
|
||||
import { UniqueKeysComponent } from "Explorer/Panes/AddGlobalSecondaryIndexPanel/Components/UniqueKeysComponent";
|
||||
import { VectorSearchComponent } from "Explorer/Panes/AddGlobalSecondaryIndexPanel/Components/VectorSearchComponent";
|
||||
import { PanelFooterComponent } from "Explorer/Panes/PanelFooterComponent";
|
||||
import { PanelInfoErrorComponent } from "Explorer/Panes/PanelInfoErrorComponent";
|
||||
@@ -66,14 +64,13 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
const [useHashV1, setUseHashV1] = useState<boolean>();
|
||||
const [enableDedicatedThroughput, setEnabledDedicatedThroughput] = useState<boolean>();
|
||||
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>();
|
||||
const [uniqueKeys, setUniqueKeys] = useState<string[]>([]);
|
||||
const [enableAnalyticalStore, setEnableAnalyticalStore] = useState<boolean>();
|
||||
const [vectorEmbeddingPolicy, setVectorEmbeddingPolicy] = useState<VectorEmbedding[]>();
|
||||
const [vectorIndexingPolicy, setVectorIndexingPolicy] = useState<VectorIndex[]>();
|
||||
const [vectorPolicyValidated, setVectorPolicyValidated] = useState<boolean>();
|
||||
const [vectorEmbeddingPolicy, setVectorEmbeddingPolicy] = useState<VectorEmbedding[]>([]);
|
||||
const [vectorIndexingPolicy, setVectorIndexingPolicy] = useState<VectorIndex[]>([]);
|
||||
const [vectorPolicyValidated, setVectorPolicyValidated] = useState<boolean>(true);
|
||||
const [fullTextPolicy, setFullTextPolicy] = useState<FullTextPolicy>(FullTextPolicyDefault);
|
||||
const [fullTextIndexes, setFullTextIndexes] = useState<FullTextIndex[]>();
|
||||
const [fullTextPolicyValidated, setFullTextPolicyValidated] = useState<boolean>();
|
||||
const [fullTextIndexes, setFullTextIndexes] = useState<FullTextIndex[]>([]);
|
||||
const [fullTextPolicyValidated, setFullTextPolicyValidated] = useState<boolean>(true);
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [showErrorDetails, setShowErrorDetails] = useState<boolean>();
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||
@@ -109,17 +106,12 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
}, [errorMessage]);
|
||||
|
||||
let globalSecondaryIndexThroughput: number;
|
||||
let isGlobalSecondaryIndexAutoscale: boolean;
|
||||
let isCostAcknowledged: boolean;
|
||||
|
||||
const globalSecondaryIndexThroughputOnChange = (globalSecondaryIndexThroughputValue: number): void => {
|
||||
globalSecondaryIndexThroughput = globalSecondaryIndexThroughputValue;
|
||||
};
|
||||
|
||||
const isGlobalSecondaryIndexAutoscaleOnChange = (isGlobalSecondaryIndexAutoscaleValue: boolean): void => {
|
||||
isGlobalSecondaryIndexAutoscale = isGlobalSecondaryIndexAutoscaleValue;
|
||||
};
|
||||
|
||||
const isCostAknowledgedOnChange = (isCostAcknowledgedValue: boolean): void => {
|
||||
isCostAcknowledged = isCostAcknowledgedValue;
|
||||
};
|
||||
@@ -178,9 +170,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
}
|
||||
|
||||
if (globalSecondaryIndexThroughput > CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) {
|
||||
const errorMessage = isGlobalSecondaryIndexAutoscale
|
||||
? "Please acknowledge the estimated monthly spend."
|
||||
: "Please acknowledge the estimated daily spend.";
|
||||
const errorMessage: string = "Please acknowledge the estimated monthly spend.";
|
||||
setErrorMessage(errorMessage);
|
||||
return false;
|
||||
}
|
||||
@@ -221,7 +211,6 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
|
||||
const partitionKeyTrimmed: string = partitionKey.trim();
|
||||
|
||||
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = parseUniqueKeys(uniqueKeys);
|
||||
const partitionKeyVersion = useHashV1 ? undefined : 2;
|
||||
const partitionKeyPaths: DataModels.PartitionKey = partitionKeyTrimmed
|
||||
? {
|
||||
@@ -256,9 +245,8 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
collection: {
|
||||
id: globalSecondaryIdTrimmed,
|
||||
throughput: globalSecondaryIndexThroughput,
|
||||
isAutoscale: isGlobalSecondaryIndexAutoscale,
|
||||
isAutoscale: true,
|
||||
partitionKeyPaths,
|
||||
uniqueKeyPolicy,
|
||||
collectionWithDedicatedThroughput: enableDedicatedThroughput,
|
||||
},
|
||||
subscriptionQuotaId: userContext.quotaId,
|
||||
@@ -268,28 +256,17 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, telemetryData);
|
||||
const databaseLevelThroughput: boolean = isSelectedSourceContainerSharedThroughput() && !enableDedicatedThroughput;
|
||||
|
||||
let offerThroughput: number;
|
||||
let autoPilotMaxThroughput: number;
|
||||
|
||||
if (!databaseLevelThroughput) {
|
||||
if (isGlobalSecondaryIndexAutoscale) {
|
||||
autoPilotMaxThroughput = globalSecondaryIndexThroughput;
|
||||
} else {
|
||||
offerThroughput = globalSecondaryIndexThroughput;
|
||||
}
|
||||
}
|
||||
|
||||
const createGlobalSecondaryIndexParams: DataModels.CreateMaterializedViewsParams = {
|
||||
materializedViewId: globalSecondaryIdTrimmed,
|
||||
materializedViewDefinition: globalSecondaryIndexDefinition,
|
||||
databaseId: selectedSourceContainer.databaseId,
|
||||
databaseLevelThroughput: databaseLevelThroughput,
|
||||
offerThroughput: offerThroughput,
|
||||
autoPilotMaxThroughput: autoPilotMaxThroughput,
|
||||
...(!databaseLevelThroughput && {
|
||||
autoPilotMaxThroughput: globalSecondaryIndexThroughput,
|
||||
}),
|
||||
analyticalStorageTtl: getAnalyticalStorageTtl(),
|
||||
indexingPolicy: indexingPolicy,
|
||||
partitionKey: partitionKeyPaths,
|
||||
uniqueKeyPolicy: uniqueKeyPolicy,
|
||||
vectorEmbeddingPolicy: vectorEmbeddingPolicyFinal,
|
||||
fullTextPolicy: fullTextPolicy,
|
||||
};
|
||||
@@ -395,12 +372,10 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
|
||||
isSelectedSourceContainerSharedThroughput,
|
||||
showCollectionThroughputInput,
|
||||
globalSecondaryIndexThroughputOnChange,
|
||||
isGlobalSecondaryIndexAutoscaleOnChange,
|
||||
setIsThroughputCapExceeded,
|
||||
isCostAknowledgedOnChange,
|
||||
}}
|
||||
/>
|
||||
<UniqueKeysComponent {...{ uniqueKeys, setUniqueKeys }} />
|
||||
{shouldShowAnalyticalStoreOptions() && (
|
||||
<AnalyticalStoreComponent {...{ explorer, enableAnalyticalStore, setEnableAnalyticalStore }} />
|
||||
)}
|
||||
|
||||
@@ -12,7 +12,6 @@ export interface ThroughputComponentProps {
|
||||
isSelectedSourceContainerSharedThroughput: () => boolean;
|
||||
showCollectionThroughputInput: () => boolean;
|
||||
globalSecondaryIndexThroughputOnChange: (globalSecondaryIndexThroughputValue: number) => void;
|
||||
isGlobalSecondaryIndexAutoscaleOnChange: (isGlobalSecondaryIndexAutoscaleValue: boolean) => void;
|
||||
setIsThroughputCapExceeded: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isCostAknowledgedOnChange: (isCostAknowledgedValue: boolean) => void;
|
||||
}
|
||||
@@ -24,7 +23,6 @@ export const ThroughputComponent = (props: ThroughputComponentProps): JSX.Elemen
|
||||
isSelectedSourceContainerSharedThroughput,
|
||||
showCollectionThroughputInput,
|
||||
globalSecondaryIndexThroughputOnChange,
|
||||
isGlobalSecondaryIndexAutoscaleOnChange,
|
||||
setIsThroughputCapExceeded,
|
||||
isCostAknowledgedOnChange,
|
||||
} = props;
|
||||
@@ -52,12 +50,11 @@ export const ThroughputComponent = (props: ThroughputComponentProps): JSX.Elemen
|
||||
isSharded={false}
|
||||
isFreeTier={isFreeTierAccount()}
|
||||
isQuickstart={false}
|
||||
isGlobalSecondaryIndex={true}
|
||||
setThroughputValue={(throughput: number) => {
|
||||
globalSecondaryIndexThroughputOnChange(throughput);
|
||||
}}
|
||||
setIsAutoscale={(isAutoscale: boolean) => {
|
||||
isGlobalSecondaryIndexAutoscaleOnChange(isAutoscale);
|
||||
}}
|
||||
setIsAutoscale={() => {}}
|
||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) => {
|
||||
setIsThroughputCapExceeded(isThroughputCapExceeded);
|
||||
}}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { ActionButton, IconButton, Stack } from "@fluentui/react";
|
||||
import { UniqueKeysHeader } from "Explorer/Panes/AddCollectionPanel/AddCollectionPanelUtility";
|
||||
import React from "react";
|
||||
import { userContext } from "UserContext";
|
||||
|
||||
export interface UniqueKeysComponentProps {
|
||||
uniqueKeys: string[];
|
||||
setUniqueKeys: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}
|
||||
|
||||
export const UniqueKeysComponent = (props: UniqueKeysComponentProps): JSX.Element => {
|
||||
const { uniqueKeys, setUniqueKeys } = props;
|
||||
|
||||
const updateUniqueKeysOnChange = (value: string, uniqueKeyToReplaceIndex: number): void => {
|
||||
const updatedUniqueKeys = uniqueKeys.map((uniqueKey: string, uniqueKeyIndex: number) => {
|
||||
if (uniqueKeyToReplaceIndex === uniqueKeyIndex) {
|
||||
return value;
|
||||
}
|
||||
return uniqueKey;
|
||||
});
|
||||
setUniqueKeys(updatedUniqueKeys);
|
||||
};
|
||||
|
||||
const deleteUniqueKeyOnClick = (uniqueKeyToDeleteIndex: number): void => {
|
||||
const updatedUniqueKeys = uniqueKeys.filter((_, uniqueKeyIndex) => uniqueKeyToDeleteIndex !== uniqueKeyIndex);
|
||||
setUniqueKeys(updatedUniqueKeys);
|
||||
};
|
||||
|
||||
const addUniqueKeyOnClick = (): void => {
|
||||
setUniqueKeys([...uniqueKeys, ""]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{UniqueKeysHeader()}
|
||||
|
||||
{uniqueKeys.map((uniqueKey: string, uniqueKeyIndex: number): JSX.Element => {
|
||||
return (
|
||||
<Stack style={{ marginBottom: 8 }} key={`uniqueKey-${uniqueKeyIndex}`} horizontal>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder={
|
||||
userContext.apiType === "Mongo"
|
||||
? "Comma separated paths e.g. firstName,address.zipCode"
|
||||
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
||||
}
|
||||
className="panelTextField"
|
||||
autoFocus
|
||||
value={uniqueKey}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateUniqueKeysOnChange(event.target.value, uniqueKeyIndex);
|
||||
}}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Delete" }}
|
||||
style={{ height: 27 }}
|
||||
onClick={() => {
|
||||
deleteUniqueKeyOnClick(uniqueKeyIndex);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
|
||||
<ActionButton
|
||||
iconProps={{ iconName: "Add" }}
|
||||
styles={{ root: { padding: 0 }, label: { fontSize: 12 } }}
|
||||
onClick={() => {
|
||||
addUniqueKeyOnClick();
|
||||
}}
|
||||
>
|
||||
Add unique key
|
||||
</ActionButton>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -137,16 +137,11 @@ exports[`AddGlobalSecondaryIndexPanel render default panel 1`] = `
|
||||
<ThroughputComponent
|
||||
globalSecondaryIndexThroughputOnChange={[Function]}
|
||||
isCostAknowledgedOnChange={[Function]}
|
||||
isGlobalSecondaryIndexAutoscaleOnChange={[Function]}
|
||||
isSelectedSourceContainerSharedThroughput={[Function]}
|
||||
setEnabledDedicatedThroughput={[Function]}
|
||||
setIsThroughputCapExceeded={[Function]}
|
||||
showCollectionThroughputInput={[Function]}
|
||||
/>
|
||||
<UniqueKeysComponent
|
||||
setUniqueKeys={[Function]}
|
||||
uniqueKeys={[]}
|
||||
/>
|
||||
<AnalyticalStoreComponent
|
||||
explorer={
|
||||
Explorer {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||
import { Platform, configContext } from "ConfigContext";
|
||||
import { useDialog } from "Explorer/Controls/Dialog";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||
import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||
import {
|
||||
AppStateComponentNames,
|
||||
deleteAllStates,
|
||||
@@ -607,441 +607,447 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
||||
return (
|
||||
<RightPaneForm {...genericPaneProps}>
|
||||
<div className={`paneMainContent ${styles.container}`}>
|
||||
<Accordion className={`customAccordion ${styles.firstItem}`}>
|
||||
{shouldShowQueryPageOptions && (
|
||||
<AccordionItem value="1">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Page Options</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as
|
||||
many query results per page.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="pageOptions"
|
||||
selectedKey={pageOption}
|
||||
options={pageOptionList}
|
||||
styles={choiceButtonStyles}
|
||||
onChange={handleOnPageOptionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={`tabs ${styles.settingsSectionContainer}`}>
|
||||
{isCustomPageOptionSelected() && (
|
||||
<div className="tabcontent">
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Query results per page{" "}
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Enter the number of query results that should be shown per page.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
|
||||
<SpinButton
|
||||
ariaLabel="Custom query items per page"
|
||||
value={"" + customItemPerPage}
|
||||
onIncrement={(newValue) => {
|
||||
setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage);
|
||||
}}
|
||||
onDecrement={(newValue) => setCustomItemPerPage(parseInt(newValue) - 1 || customItemPerPage)}
|
||||
onValidate={(newValue) => setCustomItemPerPage(parseInt(newValue) || customItemPerPage)}
|
||||
min={1}
|
||||
step={1}
|
||||
className="textfontclr"
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{showEnableEntraIdRbac && (
|
||||
<AccordionItem value="2">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable Entra ID RBAC</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra ID
|
||||
RBAC.
|
||||
<a
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{" "}
|
||||
Learn more{" "}
|
||||
</a>
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="enableDataPlaneRBACOptions"
|
||||
options={dataPlaneRBACOptionsList}
|
||||
styles={choiceButtonStyles}
|
||||
selectedKey={enableDataPlaneRBACOption}
|
||||
onChange={handleOnDataPlaneRBACOptionChange}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && !isFabric() && (
|
||||
<AccordionItem value="3">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Region Selection</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Changes region the Cosmos Client uses to access account.
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Select Region</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Changes the account endpoint used to perform client operations.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<Dropdown
|
||||
placeholder={
|
||||
selectedRegionalEndpoint
|
||||
? regionOptions.find((option) => option.key === selectedRegionalEndpoint)?.text
|
||||
: regionOptions[0]?.text
|
||||
}
|
||||
onChange={handleOnSelectedRegionOptionChange}
|
||||
options={regionOptions}
|
||||
styles={{ root: { marginBottom: "10px" } }}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{userContext.apiType === "SQL" && !isEmulator && (
|
||||
<>
|
||||
<AccordionItem value="4">
|
||||
{!isFabricNative() && (
|
||||
<Accordion className={`customAccordion ${styles.firstItem}`}>
|
||||
{shouldShowQueryPageOptions && (
|
||||
<AccordionItem value="1">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Query Timeout</div>
|
||||
<div className={styles.header}>Page Options</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
When a query reaches a specified time limit, a popup with an option to cancel the query will show
|
||||
unless automatic cancellation has been enabled.
|
||||
</div>
|
||||
<Toggle
|
||||
styles={toggleStyles}
|
||||
label="Enable query timeout"
|
||||
onChange={handleOnQueryTimeoutToggleChange}
|
||||
defaultChecked={queryTimeoutEnabled}
|
||||
/>
|
||||
</div>
|
||||
{queryTimeoutEnabled && (
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<SpinButton
|
||||
label="Query timeout (ms)"
|
||||
labelPosition={Position.top}
|
||||
defaultValue={(queryTimeout || 5000).toString()}
|
||||
min={100}
|
||||
step={1000}
|
||||
onChange={handleOnQueryTimeoutSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<Toggle
|
||||
label="Automatically cancel query after timeout"
|
||||
styles={toggleStyles}
|
||||
onChange={handleOnAutomaticallyCancelQueryToggleChange}
|
||||
defaultChecked={automaticallyCancelQueryAfterTimeout}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="5">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>RU Limit</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
If a query exceeds a configured RU limit, the query will be aborted.
|
||||
</div>
|
||||
<Toggle
|
||||
styles={toggleStyles}
|
||||
label="Enable RU limit"
|
||||
onChange={handleOnRUThresholdToggleChange}
|
||||
defaultChecked={ruThresholdEnabled}
|
||||
/>
|
||||
</div>
|
||||
{ruThresholdEnabled && (
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<SpinButton
|
||||
label="RU Limit (RU)"
|
||||
labelPosition={Position.top}
|
||||
defaultValue={(ruThreshold || DefaultRUThreshold).toString()}
|
||||
min={1}
|
||||
step={1000}
|
||||
onChange={handleOnRUThresholdSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="6">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Default Query Results View</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Select the default view to use when displaying query results.
|
||||
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as
|
||||
many query results per page.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="defaultQueryResultsView"
|
||||
selectedKey={defaultQueryResultsView}
|
||||
options={defaultQueryResultsViewOptionList}
|
||||
ariaLabelledBy="pageOptions"
|
||||
selectedKey={pageOption}
|
||||
options={pageOptionList}
|
||||
styles={choiceButtonStyles}
|
||||
onChange={handleOnDefaultQueryResultsViewChange}
|
||||
onChange={handleOnPageOptionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={`tabs ${styles.settingsSectionContainer}`}>
|
||||
{isCustomPageOptionSelected() && (
|
||||
<div className="tabcontent">
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Query results per page{" "}
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Enter the number of query results that should be shown per page.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
|
||||
<SpinButton
|
||||
ariaLabel="Custom query items per page"
|
||||
value={"" + customItemPerPage}
|
||||
onIncrement={(newValue) => {
|
||||
setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage);
|
||||
}}
|
||||
onDecrement={(newValue) => setCustomItemPerPage(parseInt(newValue) - 1 || customItemPerPage)}
|
||||
onValidate={(newValue) => setCustomItemPerPage(parseInt(newValue) || customItemPerPage)}
|
||||
min={1}
|
||||
step={1}
|
||||
className="textfontclr"
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{showEnableEntraIdRbac && (
|
||||
<AccordionItem value="2">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable Entra ID RBAC</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra
|
||||
ID RBAC.
|
||||
<a
|
||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{" "}
|
||||
Learn more{" "}
|
||||
</a>
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="enableDataPlaneRBACOptions"
|
||||
options={dataPlaneRBACOptionsList}
|
||||
styles={choiceButtonStyles}
|
||||
selectedKey={enableDataPlaneRBACOption}
|
||||
onChange={handleOnDataPlaneRBACOptionChange}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
{userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && !isFabric() && (
|
||||
<AccordionItem value="3">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Region Selection</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Changes region the Cosmos Client uses to access account.
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Select Region</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Changes the account endpoint used to perform client operations.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<Dropdown
|
||||
placeholder={
|
||||
selectedRegionalEndpoint
|
||||
? regionOptions.find((option) => option.key === selectedRegionalEndpoint)?.text
|
||||
: regionOptions[0]?.text
|
||||
}
|
||||
onChange={handleOnSelectedRegionOptionChange}
|
||||
options={regionOptions}
|
||||
styles={{ root: { marginBottom: "10px" } }}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{userContext.apiType === "SQL" && !isEmulator && (
|
||||
<>
|
||||
<AccordionItem value="4">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Query Timeout</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
When a query reaches a specified time limit, a popup with an option to cancel the query will
|
||||
show unless automatic cancellation has been enabled.
|
||||
</div>
|
||||
<Toggle
|
||||
styles={toggleStyles}
|
||||
label="Enable query timeout"
|
||||
onChange={handleOnQueryTimeoutToggleChange}
|
||||
defaultChecked={queryTimeoutEnabled}
|
||||
/>
|
||||
</div>
|
||||
{queryTimeoutEnabled && (
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<SpinButton
|
||||
label="Query timeout (ms)"
|
||||
labelPosition={Position.top}
|
||||
defaultValue={(queryTimeout || 5000).toString()}
|
||||
min={100}
|
||||
step={1000}
|
||||
onChange={handleOnQueryTimeoutSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<Toggle
|
||||
label="Automatically cancel query after timeout"
|
||||
styles={toggleStyles}
|
||||
onChange={handleOnAutomaticallyCancelQueryToggleChange}
|
||||
defaultChecked={automaticallyCancelQueryAfterTimeout}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
|
||||
{showRetrySettings && (
|
||||
<AccordionItem value="7">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Retry Settings</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Retry policy associated with throttled requests during CosmosDB queries.
|
||||
<AccordionItem value="5">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>RU Limit</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
If a query exceeds a configured RU limit, the query will be aborted.
|
||||
</div>
|
||||
<Toggle
|
||||
styles={toggleStyles}
|
||||
label="Enable RU limit"
|
||||
onChange={handleOnRUThresholdToggleChange}
|
||||
defaultChecked={ruThresholdEnabled}
|
||||
/>
|
||||
</div>
|
||||
{ruThresholdEnabled && (
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<SpinButton
|
||||
label="RU Limit (RU)"
|
||||
labelPosition={Position.top}
|
||||
defaultValue={(ruThreshold || DefaultRUThreshold).toString()}
|
||||
min={1}
|
||||
step={1000}
|
||||
onChange={handleOnRUThresholdSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="6">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Default Query Results View</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Select the default view to use when displaying query results.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="defaultQueryResultsView"
|
||||
selectedKey={defaultQueryResultsView}
|
||||
options={defaultQueryResultsViewOptionList}
|
||||
styles={choiceButtonStyles}
|
||||
onChange={handleOnDefaultQueryResultsViewChange}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{showRetrySettings && (
|
||||
<AccordionItem value="7">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Retry Settings</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Retry policy associated with throttled requests during CosmosDB queries.
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Max retry attempts</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Max number of retries to be performed for a request. Default value 9.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<SpinButton
|
||||
labelPosition={Position.top}
|
||||
min={1}
|
||||
step={1}
|
||||
value={"" + retryAttempts}
|
||||
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
|
||||
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
|
||||
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Fixed retry interval (ms)</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned
|
||||
as part of the response. Default value is 0 milliseconds.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<SpinButton
|
||||
labelPosition={Position.top}
|
||||
min={1000}
|
||||
step={1000}
|
||||
value={"" + retryInterval}
|
||||
onChange={handleOnRetryIntervalSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
|
||||
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
|
||||
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Max wait time (s)</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
|
||||
seconds.
|
||||
</InfoTooltip>
|
||||
</div>
|
||||
<SpinButton
|
||||
labelPosition={Position.top}
|
||||
min={1}
|
||||
step={1}
|
||||
value={"" + MaxWaitTimeInSeconds}
|
||||
onChange={handleOnMaxWaitTimeSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
onIncrement={(newValue) =>
|
||||
setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)
|
||||
}
|
||||
onDecrement={(newValue) =>
|
||||
setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)
|
||||
}
|
||||
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Max retry attempts</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Max number of retries to be performed for a request. Default value 9.
|
||||
</InfoTooltip>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{!isEmulator && (
|
||||
<AccordionItem value="8">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable container pagination</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel="Enable container pagination"
|
||||
checked={containerPaginationEnabled}
|
||||
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
|
||||
label="Enable container pagination"
|
||||
/>
|
||||
</div>
|
||||
<SpinButton
|
||||
labelPosition={Position.top}
|
||||
min={1}
|
||||
step={1}
|
||||
value={"" + retryAttempts}
|
||||
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
|
||||
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
|
||||
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Fixed retry interval (ms)</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned
|
||||
as part of the response. Default value is 0 milliseconds.
|
||||
</InfoTooltip>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowCrossPartitionOption && (
|
||||
<AccordionItem value="9">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable cross-partition query</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Send more than one request while executing a query. More than one request is necessary if the
|
||||
query is not scoped to single partition key value.
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel="Enable cross partition query"
|
||||
checked={crossPartitionQueryEnabled}
|
||||
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
|
||||
label="Enable cross-partition query"
|
||||
/>
|
||||
</div>
|
||||
<SpinButton
|
||||
labelPosition={Position.top}
|
||||
min={1000}
|
||||
step={1000}
|
||||
value={"" + retryInterval}
|
||||
onChange={handleOnRetryIntervalSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1000"
|
||||
decrementButtonAriaLabel="Decrease value by 1000"
|
||||
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
|
||||
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
|
||||
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
<div>
|
||||
<span className={styles.subHeader}>Max wait time (s)</span>
|
||||
<InfoTooltip className={styles.headerIcon}>
|
||||
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
|
||||
seconds.
|
||||
</InfoTooltip>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowParallelismOption && (
|
||||
<AccordionItem value="10">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Max degree of parallelism</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Gets or sets the number of concurrent operations run client side during parallel query execution.
|
||||
A positive property value limits the number of concurrent operations to the set value. If it is
|
||||
set to less than 0, the system automatically decides the number of concurrent operations to run.
|
||||
</div>
|
||||
<SpinButton
|
||||
min={-1}
|
||||
step={1}
|
||||
className="textfontclr"
|
||||
role="textbox"
|
||||
id="max-degree"
|
||||
value={"" + maxDegreeOfParallelism}
|
||||
onIncrement={(newValue) =>
|
||||
setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)
|
||||
}
|
||||
onDecrement={(newValue) =>
|
||||
setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)
|
||||
}
|
||||
onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)}
|
||||
ariaLabel="Max degree of parallelism"
|
||||
label="Max degree of parallelism"
|
||||
/>
|
||||
</div>
|
||||
<SpinButton
|
||||
labelPosition={Position.top}
|
||||
min={1}
|
||||
step={1}
|
||||
value={"" + MaxWaitTimeInSeconds}
|
||||
onChange={handleOnMaxWaitTimeSpinButtonChange}
|
||||
incrementButtonAriaLabel="Increase value by 1"
|
||||
decrementButtonAriaLabel="Decrease value by 1"
|
||||
onIncrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)}
|
||||
onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)}
|
||||
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)}
|
||||
styles={spinButtonStyles}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{!isEmulator && (
|
||||
<AccordionItem value="8">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable container pagination</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowPriorityLevelOption && (
|
||||
<AccordionItem value="11">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Priority Level</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
|
||||
Execution. If "None" is selected, Data Explorer will not specify priority level, and the
|
||||
server-side default priority level will be used.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="priorityLevel"
|
||||
selectedKey={priorityLevel}
|
||||
options={priorityLevelOptionList}
|
||||
styles={choiceButtonStyles}
|
||||
onChange={handleOnPriorityLevelOptionChange}
|
||||
/>
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel="Enable container pagination"
|
||||
checked={containerPaginationEnabled}
|
||||
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
|
||||
label="Enable container pagination"
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowCrossPartitionOption && (
|
||||
<AccordionItem value="9">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable cross-partition query</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Send more than one request while executing a query. More than one request is necessary if the query
|
||||
is not scoped to single partition key value.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowGraphAutoVizOption && (
|
||||
<AccordionItem value="12">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Display Gremlin query results as: </div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Select Graph to automatically visualize the query results as a Graph or JSON to display the
|
||||
results as JSON.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
selectedKey={graphAutoVizDisabled}
|
||||
options={graphAutoOptionList}
|
||||
onChange={handleOnGremlinChange}
|
||||
aria-label="Graph Auto-visualization"
|
||||
/>
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel="Enable cross partition query"
|
||||
checked={crossPartitionQueryEnabled}
|
||||
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
|
||||
label="Enable cross-partition query"
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowParallelismOption && (
|
||||
<AccordionItem value="10">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Max degree of parallelism</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Gets or sets the number of concurrent operations run client side during parallel query execution. A
|
||||
positive property value limits the number of concurrent operations to the set value. If it is set to
|
||||
less than 0, the system automatically decides the number of concurrent operations to run.
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowCopilotSampleDBOption && (
|
||||
<AccordionItem value="13">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable sample database</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
This is a sample database and collection with synthetic product data you can use to explore using
|
||||
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
|
||||
is created by, and maintained by Microsoft at no cost to you.
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel="Enable sample db for Query Advisor"
|
||||
checked={copilotSampleDBEnabled}
|
||||
onChange={handleSampleDatabaseChange}
|
||||
label="Enable sample database"
|
||||
/>
|
||||
</div>
|
||||
<SpinButton
|
||||
min={-1}
|
||||
step={1}
|
||||
className="textfontclr"
|
||||
role="textbox"
|
||||
id="max-degree"
|
||||
value={"" + maxDegreeOfParallelism}
|
||||
onIncrement={(newValue) =>
|
||||
setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)
|
||||
}
|
||||
onDecrement={(newValue) =>
|
||||
setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)
|
||||
}
|
||||
onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)}
|
||||
ariaLabel="Max degree of parallelism"
|
||||
label="Max degree of parallelism"
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowPriorityLevelOption && (
|
||||
<AccordionItem value="11">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Priority Level</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
|
||||
Execution. If "None" is selected, Data Explorer will not specify priority level, and the
|
||||
server-side default priority level will be used.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="priorityLevel"
|
||||
selectedKey={priorityLevel}
|
||||
options={priorityLevelOptionList}
|
||||
styles={choiceButtonStyles}
|
||||
onChange={handleOnPriorityLevelOptionChange}
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowGraphAutoVizOption && (
|
||||
<AccordionItem value="12">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Display Gremlin query results as: </div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
Select Graph to automatically visualize the query results as a Graph or JSON to display the results
|
||||
as JSON.
|
||||
</div>
|
||||
<ChoiceGroup
|
||||
selectedKey={graphAutoVizDisabled}
|
||||
options={graphAutoOptionList}
|
||||
onChange={handleOnGremlinChange}
|
||||
aria-label="Graph Auto-visualization"
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
{shouldShowCopilotSampleDBOption && (
|
||||
<AccordionItem value="13">
|
||||
<AccordionHeader>
|
||||
<div className={styles.header}>Enable sample database</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className={styles.settingsSectionContainer}>
|
||||
<div className={styles.settingsSectionDescription}>
|
||||
This is a sample database and collection with synthetic product data you can use to explore using
|
||||
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
|
||||
is created by, and maintained by Microsoft at no cost to you.
|
||||
</div>
|
||||
<Checkbox
|
||||
styles={{
|
||||
label: { padding: 0 },
|
||||
}}
|
||||
className="padding"
|
||||
ariaLabel="Enable sample db for Query Advisor"
|
||||
checked={copilotSampleDBEnabled}
|
||||
onChange={handleSampleDatabaseChange}
|
||||
label="Enable sample database"
|
||||
/>
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
</Accordion>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
)}
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
<div className="settingsSection">
|
||||
<div className="settingsSectionPart">
|
||||
|
||||
@@ -22,12 +22,17 @@ export const DeletePopup = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={showDeletePopup} styles={{ main: { minHeight: "122px", minWidth: "880px" } }}>
|
||||
<Modal
|
||||
isOpen={showDeletePopup}
|
||||
styles={{ main: { minHeight: "122px", minWidth: "880px" } }}
|
||||
titleAriaId="deleteDialogTitle"
|
||||
subtitleAriaId="deleteDialogSubTitle"
|
||||
>
|
||||
<Stack style={{ padding: "16px 24px", height: "auto" }}>
|
||||
<Text style={{ height: 24, fontSize: "18px" }}>
|
||||
<Text id="deleteDialogTitle" style={{ height: 24, fontSize: "18px" }}>
|
||||
<b>Delete code?</b>
|
||||
</Text>
|
||||
<Text style={{ marginTop: 10, marginBottom: 20 }}>
|
||||
<Text id="deleteDialogSubTitle" style={{ marginTop: 10, marginBottom: 20 }}>
|
||||
This will clear the query from the query builder pane along with all comments and also reset the prompt pane
|
||||
</Text>
|
||||
<Stack horizontal tokens={{ childrenGap: 10 }} horizontalAlign="start">
|
||||
|
||||
@@ -11,6 +11,8 @@ exports[`Delete Popup snapshot test should not render when showDeletePopup is fa
|
||||
},
|
||||
}
|
||||
}
|
||||
subtitleAriaId="deleteDialogSubTitle"
|
||||
titleAriaId="deleteDialogTitle"
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
@@ -21,6 +23,7 @@ exports[`Delete Popup snapshot test should not render when showDeletePopup is fa
|
||||
}
|
||||
>
|
||||
<Text
|
||||
id="deleteDialogTitle"
|
||||
style={
|
||||
{
|
||||
"fontSize": "18px",
|
||||
@@ -33,6 +36,7 @@ exports[`Delete Popup snapshot test should not render when showDeletePopup is fa
|
||||
</b>
|
||||
</Text>
|
||||
<Text
|
||||
id="deleteDialogSubTitle"
|
||||
style={
|
||||
{
|
||||
"marginBottom": 20,
|
||||
@@ -89,6 +93,8 @@ exports[`Delete Popup snapshot test should render when showDeletePopup is true 1
|
||||
},
|
||||
}
|
||||
}
|
||||
subtitleAriaId="deleteDialogSubTitle"
|
||||
titleAriaId="deleteDialogTitle"
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
@@ -99,6 +105,7 @@ exports[`Delete Popup snapshot test should render when showDeletePopup is true 1
|
||||
}
|
||||
>
|
||||
<Text
|
||||
id="deleteDialogTitle"
|
||||
style={
|
||||
{
|
||||
"fontSize": "18px",
|
||||
@@ -111,6 +118,7 @@ exports[`Delete Popup snapshot test should render when showDeletePopup is true 1
|
||||
</b>
|
||||
</Text>
|
||||
<Text
|
||||
id="deleteDialogSubTitle"
|
||||
style={
|
||||
{
|
||||
"marginBottom": 20,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BackendDefaults } from "Common/Constants";
|
||||
import { createCollection } from "Common/dataAccess/createCollection";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
@@ -35,6 +36,11 @@ export const createContainer = async (
|
||||
collectionId: containerName,
|
||||
databaseId: databaseName,
|
||||
databaseLevelThroughput: false,
|
||||
partitionKey: {
|
||||
paths: [`/${SAMPLE_DATA_PARTITION_KEY}`],
|
||||
kind: "Hash",
|
||||
version: BackendDefaults.partitionKeyVersion,
|
||||
},
|
||||
};
|
||||
await createCollection(createRequest);
|
||||
await explorer.refreshAllDatabases();
|
||||
@@ -47,6 +53,8 @@ export const createContainer = async (
|
||||
return newCollection;
|
||||
};
|
||||
|
||||
const SAMPLE_DATA_PARTITION_KEY = "category"; // This pkey is specifically set for queryCopilotSampleData.json below
|
||||
|
||||
export const importData = async (collection: ViewModels.Collection): Promise<void> => {
|
||||
// TODO: keep same chunk as ContainerSampleGenerator
|
||||
const dataFileContent = await import(
|
||||
|
||||
@@ -817,7 +817,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
|
||||
private vcoreMongoNextStepItems: { link: string; title: string; description: string }[] = [
|
||||
{
|
||||
link: "https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/vcore/how-to-migrate-native-tools?tabs=export-import",
|
||||
link: "https://learn.microsoft.com/azure/cosmos-db/mongodb/vcore/migration-options",
|
||||
title: "Migrate Data",
|
||||
description: "",
|
||||
},
|
||||
|
||||
@@ -773,8 +773,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
[_collection, _partitionKey],
|
||||
);
|
||||
const partitionKeyPropertyHeaders: string[] = useMemo(
|
||||
() => (partitionKey?.systemKey ? [] : _collection?.partitionKeyPropertyHeaders || partitionKey?.paths),
|
||||
[_collection?.partitionKeyPropertyHeaders, partitionKey?.paths, partitionKey?.systemKey],
|
||||
() =>
|
||||
isPreferredApiMongoDB && partitionKey?.systemKey
|
||||
? []
|
||||
: _collection?.partitionKeyPropertyHeaders || partitionKey?.paths,
|
||||
[_collection?.partitionKeyPropertyHeaders, partitionKey?.paths, partitionKey?.systemKey, isPreferredApiMongoDB],
|
||||
);
|
||||
let partitionKeyProperties = useMemo(() => {
|
||||
return partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) =>
|
||||
@@ -2116,6 +2119,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
/>
|
||||
<Button
|
||||
appearance="primary"
|
||||
data-test={"DocumentsTab/ApplyFilter"}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
if (isExecuting) {
|
||||
@@ -2188,6 +2192,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
||||
{tableItems.length > 0 && (
|
||||
<a
|
||||
className={styles.loadMore}
|
||||
data-test={"DocumentsTab/LoadMore"}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => loadNextPage(documentsIterator.iterator, false)}
|
||||
|
||||
@@ -51,6 +51,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
||||
<Button
|
||||
appearance="primary"
|
||||
aria-label="Apply filter"
|
||||
data-test="DocumentsTab/ApplyFilter"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
|
||||
@@ -241,21 +241,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
|
||||
});
|
||||
|
||||
if (
|
||||
useNotebook.getState().isNotebookEnabled &&
|
||||
userContext.apiType === "Mongo" &&
|
||||
isPublicInternetAccessAllowed()
|
||||
) {
|
||||
children.push({
|
||||
label: "Schema (Preview)",
|
||||
onClick: collection.onSchemaAnalyzerClick.bind(collection),
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
||||
});
|
||||
}
|
||||
|
||||
if (userContext.apiType !== "Cassandra" || !isServerlessAccount()) {
|
||||
children.push({
|
||||
label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings",
|
||||
|
||||
@@ -338,11 +338,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
|
||||
"label": "Documents",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"isSelected": [Function],
|
||||
"label": "Schema (Preview)",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"isSelected": [Function],
|
||||
@@ -406,11 +401,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
|
||||
"label": "Documents",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"isSelected": [Function],
|
||||
"label": "Schema (Preview)",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"isSelected": [Function],
|
||||
@@ -515,11 +505,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
|
||||
"label": "Documents",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"isSelected": [Function],
|
||||
"label": "Schema (Preview)",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"id": "sampleSettings",
|
||||
"isSelected": [Function],
|
||||
@@ -610,11 +595,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
|
||||
"label": "Documents",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"isSelected": [Function],
|
||||
"label": "Schema (Preview)",
|
||||
"onClick": [Function],
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"isSelected": [Function],
|
||||
|
||||
@@ -11,7 +11,6 @@ import { getItemName } from "Utils/APITypeUtils";
|
||||
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
import React from "react";
|
||||
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||
import { Platform, configContext } from "../../ConfigContext";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -19,7 +18,6 @@ import { userContext } from "../../UserContext";
|
||||
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
|
||||
import Explorer from "../Explorer";
|
||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
export const shouldShowScriptNodes = (): boolean => {
|
||||
@@ -294,22 +292,6 @@ const buildCollectionNodeChildren = (
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection),
|
||||
});
|
||||
|
||||
if (
|
||||
isNotebookEnabled &&
|
||||
userContext.apiType === "Mongo" &&
|
||||
isPublicInternetAccessAllowed() &&
|
||||
useNotebook.getState().isPhoenixFeatures
|
||||
) {
|
||||
children.push({
|
||||
label: "Schema (Preview)",
|
||||
onClick: collection.onSchemaAnalyzerClick.bind(collection),
|
||||
isSelected: () =>
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
||||
});
|
||||
}
|
||||
|
||||
if (userContext.apiType !== "Cassandra" || !isServerlessAccount()) {
|
||||
let id = "";
|
||||
if (collection.isSampleCollection) {
|
||||
|
||||
@@ -52,22 +52,10 @@ export const allowedAadEndpoints: ReadonlyArray<string> = [
|
||||
];
|
||||
|
||||
export const defaultAllowedBackendEndpoints: ReadonlyArray<string> = [
|
||||
"https://main.documentdb.ext.azure.com",
|
||||
"https://main.documentdb.ext.azure.cn",
|
||||
"https://main.documentdb.ext.azure.us",
|
||||
"https://main.cosmos.ext.azure",
|
||||
"https://localhost:12901",
|
||||
"https://localhost:1234",
|
||||
];
|
||||
|
||||
export const PortalBackendIPs: { [key: string]: string[] } = {
|
||||
"https://main.documentdb.ext.azure.com": ["104.42.195.92", "40.76.54.131"],
|
||||
// DE doesn't talk to prod2 (main2) but it might be added
|
||||
//"https://main2.documentdb.ext.azure.com": ["104.42.196.69"],
|
||||
"https://main.documentdb.ext.azure.cn": ["139.217.8.252"],
|
||||
"https://main.documentdb.ext.azure.us": ["52.244.48.71"],
|
||||
};
|
||||
|
||||
export const PortalBackendOutboundIPs: { [key: string]: string[] } = {
|
||||
[PortalBackendEndpoints.Mpac]: ["13.91.105.215", "4.210.172.107"],
|
||||
[PortalBackendEndpoints.Prod]: ["13.88.56.148", "40.91.218.243"],
|
||||
@@ -98,14 +86,6 @@ export const defaultAllowedCassandraProxyEndpoints: ReadonlyArray<string> = [
|
||||
CassandraProxyEndpoints.Mooncake,
|
||||
];
|
||||
|
||||
export const allowedCassandraProxyEndpoints_ToBeDeprecated: ReadonlyArray<string> = [
|
||||
"https://main.documentdb.ext.azure.com",
|
||||
"https://main.documentdb.ext.azure.cn",
|
||||
"https://main.documentdb.ext.azure.us",
|
||||
"https://main.cosmos.ext.azure",
|
||||
"https://localhost:12901",
|
||||
];
|
||||
|
||||
export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
|
||||
[CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
|
||||
[CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"],
|
||||
|
||||
@@ -17,7 +17,6 @@ describe("isInvalidParentFrameOrigin", () => {
|
||||
${"https://cdb-ff-prod-pbe.cosmos.azure.us"} | ${false}
|
||||
${"https://cdb-mc-prod-pbe.cosmos.azure.cn"} | ${false}
|
||||
${"https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de"} | ${false}
|
||||
${"https://main.documentdb.ext.microsoftazure.de"} | ${false}
|
||||
${"https://random.domain"} | ${true}
|
||||
${"https://malicious.cloudapp.azure.com"} | ${true}
|
||||
${"https://malicious.germanycentral.cloudapp.microsoftazure.de"} | ${true}
|
||||
|
||||
@@ -42,214 +42,92 @@
|
||||
<div class="container-fluid">
|
||||
<ul class="nav nav-tabs qslevel">
|
||||
<li class="active">
|
||||
<a data-toggle="tab" href="#net"
|
||||
><img class="qsmenuicons" src="../images/dotnet.png" alt=".NET platform" />.NET</a
|
||||
>
|
||||
<a data-toggle="tab" href="#net">
|
||||
<img class="qsmenuicons" src="../images/dotnet.png" alt=".NET" /> .NET
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" href="#corenet"
|
||||
><img class="qsmenuicons" src="../images/dotnet.png" alt=".NET Core platform" />.NET Core</a
|
||||
>
|
||||
<a data-toggle="tab" href="#java"> <img class="qsmenuicons" src="../images/java.png" alt="Java" /> Java </a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" href="#Java"
|
||||
><img class="qsmenuicons" src="../images/java.png" alt="Java platform" />Java</a
|
||||
>
|
||||
<a data-toggle="tab" href="#nodejs">
|
||||
<img class="qsmenuicons" src="../images/nodejs.png" alt="Node.js" /> Node.js
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" href="#NodeJs"
|
||||
><img class="qsmenuicons" src="../images/nodejs.png" alt="Node.js platform" />Node.js</a
|
||||
>
|
||||
<a data-toggle="tab" href="#python">
|
||||
<img class="qsmenuicons" src="../images/python.png" alt="Python" /> Python
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" href="#Python"
|
||||
><img class="qsmenuicons" src="../images/python.png" alt="Python platform" />Python</a
|
||||
>
|
||||
<a data-toggle="tab" href="#go"> <img class="qsmenuicons" src="../images/golang.svg" alt="Go" /> Go </a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" href="#springboot">
|
||||
<img class="qsmenuicons" src="../images/springboot.svg" alt="Spring Boot" /> Spring Boot
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content tab-content-override">
|
||||
|
||||
<div class="tab-content">
|
||||
<div id="net" class="tab-pane fade in active">
|
||||
<div class="netApp">
|
||||
<div class="sampleApp">
|
||||
<div class="numbersize numbersizePadding">1</div>
|
||||
<div class="numberheading">
|
||||
Open and run a sample .NET app
|
||||
Create a new .NET app
|
||||
<p>
|
||||
We created a sample .NET app connected to your Azure Cosmos DB Emulator instance. Download, extract,
|
||||
build and run the app.
|
||||
Follow this
|
||||
<a href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-dotnet" target="_blank"
|
||||
>tutorial
|
||||
</a>
|
||||
to create a new .NET app connected to Azure Cosmos DB.
|
||||
</p>
|
||||
<a href="quickstart/DocumentDB-Quickstart-DotNet.zip"
|
||||
><button class="btncreatecoll">Download</button></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="netApp">
|
||||
<div class="numbersize">2</div>
|
||||
<div class="numberheading">
|
||||
Learn more about Azure Cosmos DB
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/dotnet"
|
||||
>Code Samples</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||
>Capacity Planner</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="corenet" class="tab-pane fade">
|
||||
<div class="netApp">
|
||||
<div class="numbersize numbersizePadding">1</div>
|
||||
<div class="numberheading">
|
||||
Open and run a sample .NET Core app
|
||||
<p>
|
||||
We created a sample .NET Core app connected to your Azure Cosmos DB Emulator instance. Download,
|
||||
extract, build and run the app.
|
||||
</p>
|
||||
<a href="quickstart/DocumentDB-Quickstart-DotNetCore.zip"
|
||||
><button class="btncreatecoll">Download</button></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="netApp">
|
||||
<div class="numbersize">2</div>
|
||||
<div class="numberheading">
|
||||
Learn more about Azure Cosmos DB.
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/dotnet"
|
||||
>Code Samples</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||
>Capacity Planner</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="learn-more"></div>
|
||||
</div>
|
||||
|
||||
<div id="Java" class="tab-pane fade">
|
||||
<div class="step1">
|
||||
<div id="java" class="tab-pane fade">
|
||||
<div class="sampleApp">
|
||||
<div class="numbersize numbersizePadding">1</div>
|
||||
<div class="numberheading">
|
||||
Open and run a sample Java app
|
||||
Create a new Java app
|
||||
<p>
|
||||
We created a sample Java app connected to your Azure Cosmos DB Emulator instance. Download, extract,
|
||||
build and run the app.
|
||||
</p>
|
||||
<a href="quickstart/DocumentDB-Quickstart-Java.zip"><button class="btncreatecoll">Download</button></a>
|
||||
<p>
|
||||
Follow instructions in the readme.md to setup prerequisites needed to run Java web apps, if you
|
||||
haven’t already.
|
||||
Follow this
|
||||
<a href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-java" target="_blank"
|
||||
>tutorial
|
||||
</a>
|
||||
to create a new Java app connected to Azure Cosmos DB.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step1">
|
||||
<div class="numbersize">2</div>
|
||||
<div class="numberheading">
|
||||
Learn more about Azure Cosmos DB.
|
||||
<ul>
|
||||
<!--<li><a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/java">Code Samples</a></li>-->
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||
>Capacity Planner</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="learn-more"></div>
|
||||
</div>
|
||||
|
||||
<div id="NodeJs" class="tab-pane fade">
|
||||
<div class="step1">
|
||||
<div id="nodejs" class="tab-pane fade">
|
||||
<div class="sampleApp">
|
||||
<div class="numbersize numbersizePadding">1</div>
|
||||
<div class="numberheading">
|
||||
Open and run a sample Node.js app
|
||||
Create a new Node.js app
|
||||
<p>
|
||||
We created a sample Node.js app connected to your Azure Cosmos DB Emulator instance. Download,
|
||||
extract, build and run the app.
|
||||
</p>
|
||||
<a href="quickstart/DocumentDB-Quickstart-NodeJs.zip"
|
||||
><button class="btncreatecoll">Download</button></a
|
||||
>
|
||||
<p>
|
||||
Run <strong>npm install</strong> and <strong>npm start</strong>, and navigate to
|
||||
<a href="http://localhost:3000" _targe="blank">http://localhost:3000</a>.
|
||||
Follow this
|
||||
<a
|
||||
href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-nodejs?pivots=programming-language-ts"
|
||||
target="_blank"
|
||||
>tutorial
|
||||
</a>
|
||||
to create a new Node.js app connected to Azure Cosmos DB.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step1">
|
||||
<div class="numbersize">2</div>
|
||||
<div class="numberheading">
|
||||
Learn more about Azure Cosmos DB.
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/nodejs"
|
||||
>Code Samples</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||
>Capacity Planner</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/stackoverflow">Forum</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="learn-more"></div>
|
||||
</div>
|
||||
|
||||
<div id="Python" class="tab-pane fade">
|
||||
<div class="pythonApp">
|
||||
<div id="python" class="tab-pane fade">
|
||||
<div class="sampleApp">
|
||||
<div class="numbersize numbersizePadding">1</div>
|
||||
<div class="numberheading">
|
||||
Create a new Python app.
|
||||
Create a new Python app
|
||||
<p>
|
||||
Follow this
|
||||
<a href="https://aka.ms/cosmos-db-emulator/tutorial/python" target="_blank">tutorial</a>
|
||||
@@ -257,42 +135,73 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="learn-more"></div>
|
||||
</div>
|
||||
|
||||
<div class="pythonApp">
|
||||
<div class="numbersize">2</div>
|
||||
<div id="go" class="tab-pane fade">
|
||||
<div class="sampleApp">
|
||||
<div class="numbersize numbersizePadding">1</div>
|
||||
<div class="numberheading">
|
||||
Learn more about Azure Cosmos DB.
|
||||
<ul>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/samples/python"
|
||||
>Code Samples</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/docs">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/pricing">Pricing</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" class="atags" href="https://aka.ms/cosmos-db-emulator/capacity-planner"
|
||||
>Capacity planner</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
class="atags"
|
||||
href="https://social.msdn.microsoft.com/forums/azure/home?forum=AzureDocumentDB"
|
||||
>Forum</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
Create a new Go app
|
||||
<p>
|
||||
Follow this
|
||||
<a href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-go" target="_blank">tutorial</a>
|
||||
to create a new Go app connected to Azure Cosmos DB.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="learn-more"></div>
|
||||
</div>
|
||||
|
||||
<div id="springboot" class="tab-pane fade">
|
||||
<div class="sampleApp">
|
||||
<div class="numbersize numbersizePadding">1</div>
|
||||
<div class="numberheading">
|
||||
Create a new Spring Boot app
|
||||
<p>
|
||||
Follow this
|
||||
<a
|
||||
href="https://learn.microsoft.com/azure/cosmos-db/nosql/tutorial-springboot-azure-kubernetes-service"
|
||||
target="_blank"
|
||||
>tutorial</a
|
||||
>
|
||||
to create a new Spring Boot app connected to Azure Cosmos DB.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="learn-more"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="learnMoreTpl">
|
||||
<div class="app-block">
|
||||
<div class="numbersize">2</div>
|
||||
<div class="numberheading">
|
||||
Learn more about Azure Cosmos DB
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://azurecosmosdb.github.io/gallery/?tags=example" target="_blank" class="atags"
|
||||
>Code Samples</a
|
||||
>
|
||||
</li>
|
||||
<li><a href="https://aka.ms/cosmos-db-emulator/docs" target="_blank" class="atags">Documentation</a></li>
|
||||
<li><a href="https://aka.ms/cosmos-db-emulator/pricing" target="_blank" class="atags">Pricing</a></li>
|
||||
<li>
|
||||
<a href="https://cosmos.azure.com/capacitycalculator/" target="_blank" class="atags">Capacity planner</a>
|
||||
</li>
|
||||
<li><a href="https://aka.ms/cosmos-db-emulator/stackoverflow" target="_blank" class="atags">Forum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll(".learn-more").forEach((slot) => {
|
||||
const node = document.getElementById("learnMoreTpl").content.cloneNode(true);
|
||||
slot.appendChild(node);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
23
test/CORSBypass.ts
Normal file
23
test/CORSBypass.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
export async function setupCORSBypass(page: Page) {
|
||||
await page.route("**/api/mongo/explorer{,/**}", async (route) => {
|
||||
const response = await route.fetch({
|
||||
headers: {
|
||||
...route.request().headers(),
|
||||
},
|
||||
});
|
||||
|
||||
await route.fulfill({
|
||||
status: response.status(),
|
||||
headers: {
|
||||
...response.headers(),
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Credentials": "*",
|
||||
},
|
||||
body: await response.body(),
|
||||
});
|
||||
});
|
||||
}
|
||||
16
test/fx.ts
16
test/fx.ts
@@ -1,4 +1,4 @@
|
||||
import { AzureCliCredential } from "@azure/identity";
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
import { Frame, Locator, Page, expect } from "@playwright/test";
|
||||
import crypto from "crypto";
|
||||
|
||||
@@ -20,8 +20,8 @@ export function generateUniqueName(baseName, options?: TestNameOptions): string
|
||||
return `${prefix}${baseName}${crypto.randomBytes(length).toString("hex")}${suffix}`;
|
||||
}
|
||||
|
||||
export function getAzureCLICredentials(): AzureCliCredential {
|
||||
return new AzureCliCredential();
|
||||
export function getAzureCLICredentials(): DefaultAzureCredential {
|
||||
return new DefaultAzureCredential();
|
||||
}
|
||||
|
||||
export async function getAzureCLICredentialsToken(): Promise<string> {
|
||||
@@ -223,6 +223,9 @@ export class DocumentsTab {
|
||||
documentsListPane: Locator;
|
||||
documentResultsPane: Locator;
|
||||
resultsEditor: Editor;
|
||||
loadMoreButton: Locator;
|
||||
filterInput: Locator;
|
||||
filterButton: Locator;
|
||||
|
||||
constructor(
|
||||
public frame: Frame,
|
||||
@@ -234,6 +237,13 @@ export class DocumentsTab {
|
||||
this.documentsListPane = this.locator.getByTestId("DocumentsTab/DocumentsPane");
|
||||
this.documentResultsPane = this.locator.getByTestId("DocumentsTab/ResultsPane");
|
||||
this.resultsEditor = new Editor(this.frame, this.documentResultsPane.getByTestId("EditorReact/Host/Loaded"));
|
||||
this.loadMoreButton = this.documentsListPane.getByTestId("DocumentsTab/LoadMore");
|
||||
this.filterInput = this.documentsFilter.getByTestId("DocumentsTab/FilterInput");
|
||||
this.filterButton = this.documentsFilter.getByTestId("DocumentsTab/ApplyFilter");
|
||||
}
|
||||
|
||||
async setFilter(text: string) {
|
||||
await this.filterInput.fill(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { setupCORSBypass } from "../CORSBypass";
|
||||
import { DataExplorer, DocumentsTab, TestAccount } from "../fx";
|
||||
import { retry, serializeMongoToJson, setPartitionKeys } from "../testData";
|
||||
import { documentTestCases } from "./testCases";
|
||||
@@ -9,7 +10,9 @@ let documentsTab: DocumentsTab = null!;
|
||||
|
||||
for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
test.describe(`Test MongoRU Documents with ${name}`, () => {
|
||||
// test.skip(true, "Temporarily disabling all tests in this spec file");
|
||||
test.beforeEach("Open documents tab", async ({ page }) => {
|
||||
await setupCORSBypass(page);
|
||||
explorer = await DataExplorer.open(page, TestAccount.MongoReadonly);
|
||||
|
||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||
@@ -24,6 +27,9 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
await documentsTab.documentsListPane.waitFor();
|
||||
await expect(documentsTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
|
||||
});
|
||||
test.afterEach(async ({ page }) => {
|
||||
await page.unrouteAll({ behavior: "ignoreErrors" });
|
||||
});
|
||||
|
||||
for (const document of documents) {
|
||||
const { documentId: docId, partitionKeys } = document;
|
||||
@@ -67,8 +73,12 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
await documentsTab.resultsEditor.setText(JSON.stringify(newDocument));
|
||||
const saveButton = await explorer.waitForCommandBarButton("Save", 5000);
|
||||
await saveButton.click({ timeout: 5000 });
|
||||
await expect(saveButton).toBeHidden({ timeout: 5000 });
|
||||
}, 3);
|
||||
|
||||
await documentsTab.setFilter(`{_id: "${newDocumentId}"}`);
|
||||
await documentsTab.filterButton.click();
|
||||
|
||||
const newSpan = documentsTab.documentsListPane.getByText(newDocumentId, { exact: true }).nth(0);
|
||||
await newSpan.waitFor();
|
||||
await newSpan.click();
|
||||
|
||||
@@ -9,6 +9,7 @@ let documentsTab: DocumentsTab = null!;
|
||||
|
||||
for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
test.describe(`Test SQL Documents with ${name}`, () => {
|
||||
// test.skip(true, "Temporarily disabling all tests in this spec file");
|
||||
test.beforeEach("Open documents tab", async ({ page }) => {
|
||||
explorer = await DataExplorer.open(page, TestAccount.SQLReadOnly);
|
||||
|
||||
@@ -26,7 +27,7 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
});
|
||||
|
||||
for (const document of documents) {
|
||||
const { documentId: docId, partitionKeys } = document;
|
||||
const { documentId: docId, partitionKeys, skipCreateDelete } = document;
|
||||
test.describe(`Document ID: ${docId}`, () => {
|
||||
test(`should load and view document ${docId}`, async () => {
|
||||
const span = documentsTab.documentsListPane.getByText(docId, { exact: true }).nth(0);
|
||||
@@ -41,7 +42,9 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
expect(resultText).not.toBeNull();
|
||||
expect(resultData?.id).toEqual(docId);
|
||||
});
|
||||
test(`should be able to create and delete new document from ${docId}`, async ({ page }) => {
|
||||
|
||||
const testOrSkip = skipCreateDelete ? test.skip : test;
|
||||
testOrSkip(`should be able to create and delete new document from ${docId}`, async ({ page }) => {
|
||||
const span = documentsTab.documentsListPane.getByText(docId, { exact: true }).nth(0);
|
||||
await span.waitFor();
|
||||
await expect(span).toBeVisible();
|
||||
@@ -50,10 +53,6 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
let newDocumentId;
|
||||
await page.waitForTimeout(5000);
|
||||
await retry(async () => {
|
||||
// const discardButton = await explorer.waitForCommandBarButton("Discard", 5000);
|
||||
// if (await discardButton.isEnabled()) {
|
||||
// await discardButton.click();
|
||||
// }
|
||||
const newDocumentButton = await explorer.waitForCommandBarButton("New Item", 5000);
|
||||
await expect(newDocumentButton).toBeVisible();
|
||||
await expect(newDocumentButton).toBeEnabled();
|
||||
@@ -71,10 +70,15 @@ for (const { name, databaseId, containerId, documents } of documentTestCases) {
|
||||
await documentsTab.resultsEditor.setText(JSON.stringify(newDocument));
|
||||
const saveButton = await explorer.waitForCommandBarButton("Save", 5000);
|
||||
await saveButton.click({ timeout: 5000 });
|
||||
await expect(saveButton).toBeHidden({ timeout: 5000 });
|
||||
}, 3);
|
||||
|
||||
await documentsTab.setFilter(`WHERE c.id = "${newDocumentId}"`);
|
||||
await documentsTab.filterButton.click();
|
||||
|
||||
const newSpan = documentsTab.documentsListPane.getByText(newDocumentId, { exact: true }).nth(0);
|
||||
await newSpan.waitFor();
|
||||
|
||||
await newSpan.click();
|
||||
await expect(documentsTab.resultsEditor.locator).toBeAttached({ timeout: 60 * 1000 });
|
||||
|
||||
|
||||
@@ -5,7 +5,24 @@ export const documentTestCases: DocumentTestCase[] = [
|
||||
name: "System Partition Key",
|
||||
databaseId: "e2etests-sql-readonly",
|
||||
containerId: "systemPartitionKey",
|
||||
documents: [{ documentId: "systempartition", partitionKeys: [] }],
|
||||
documents: [
|
||||
{
|
||||
documentId: "systempartition",
|
||||
partitionKeys: [{ key: "/_partitionKey", value: "partitionKey" }],
|
||||
skipCreateDelete: true,
|
||||
},
|
||||
{
|
||||
documentId: "systempartition_empty",
|
||||
partitionKeys: [{ key: "/_partitionKey", value: "" }],
|
||||
skipCreateDelete: true,
|
||||
},
|
||||
{
|
||||
documentId: "systempartition_null",
|
||||
partitionKeys: [{ key: "/_partitionKey", value: null }],
|
||||
skipCreateDelete: true,
|
||||
},
|
||||
{ documentId: "systempartition_missing", partitionKeys: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Single Partition Key",
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
||||
import { BulkOperationType, Container, CosmosClient, Database, JSONObject } from "@azure/cosmos";
|
||||
import crypto from "crypto";
|
||||
import { AzureIdentityCredentialAdapter } from "@azure/ms-rest-js";
|
||||
|
||||
import {
|
||||
TestAccount,
|
||||
generateUniqueName,
|
||||
getAccountName,
|
||||
getAzureCLICredentials,
|
||||
resourceGroupName,
|
||||
subscriptionId,
|
||||
TestAccount,
|
||||
} from "./fx";
|
||||
|
||||
export interface TestItem {
|
||||
@@ -26,6 +29,7 @@ export interface DocumentTestCase {
|
||||
export interface TestDocument {
|
||||
documentId: string;
|
||||
partitionKeys?: PartitionKey[];
|
||||
skipCreateDelete?: boolean;
|
||||
}
|
||||
|
||||
export interface PartitionKey {
|
||||
@@ -74,7 +78,8 @@ export async function createTestSQLContainer(includeTestData?: boolean) {
|
||||
const databaseId = generateUniqueName("db");
|
||||
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
|
||||
const credentials = getAzureCLICredentials();
|
||||
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||
const adaptedCredentials = new AzureIdentityCredentialAdapter(credentials);
|
||||
const armClient = new CosmosDBManagementClient(adaptedCredentials, subscriptionId);
|
||||
const accountName = getAccountName(TestAccount.SQL);
|
||||
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);
|
||||
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, accountName);
|
||||
|
||||
Reference in New Issue
Block a user